@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,97 @@
1
+ import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common'
2
+ import { Prisma, PrismaClient } from '@<%= npmScope %>/api/prisma'
3
+ import { CorePagingInput } from './dto/core-paging.input'
4
+ import { withOptimize } from '@prisma/extension-optimize'
5
+
6
+ @Injectable()
7
+ export class ApiCoreDataAccessService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
8
+ constructor() {
9
+ const config: Prisma.PrismaClientOptions = {
10
+ datasources: {
11
+ db: { url: `${process.env['DATABASE_URL']}?connection_limit=30` },
12
+ },
13
+ log:
14
+ process.env['LOG_PRISMA_QUERIES'] === 'true' || process.env['COUNT_PRISMA_QUERIES'] === 'true'
15
+ ? [{ emit: 'event', level: 'query' }]
16
+ : [{ emit: 'event', level: 'warn' }],
17
+ }
18
+ super(config)
19
+ this.queryCount = 0
20
+
21
+ // Only add Prisma Optimize extension in development
22
+ if (process.env['OPTIMIZE_API_KEY'] && process.env['USE_OPTIMIZE'] === 'true') {
23
+ const apiKey = process.env['OPTIMIZE_API_KEY']
24
+ if (!apiKey) {
25
+ console.warn('Not Running Prisma Optimize - No API Key Set')
26
+ }
27
+
28
+ const extendedClient = new PrismaClient(config).$extends(withOptimize({ apiKey }))
29
+ Object.assign(this, extendedClient)
30
+ }
31
+ }
32
+
33
+ public queryCount: number
34
+
35
+ public async onModuleDestroy(): Promise<void> {
36
+ await this.$disconnect()
37
+ }
38
+
39
+ public async onModuleInit(): Promise<void> {
40
+ await this.$connect()
41
+
42
+ if (process.env['LOG_PRISMA_QUERIES'] == 'true') {
43
+ this.$on('query' as never, async (e: Prisma.QueryEvent) => {
44
+ console.log(`QUERY: ${e.query} \n\nPARAMS: ${e.params}\n\n\n`)
45
+ })
46
+ }
47
+
48
+ if (process.env['COUNT_PRISMA_QUERIES'] == 'true') {
49
+ this.$on('query' as never, async () => {
50
+ this.queryCount++
51
+ })
52
+ }
53
+ }
54
+
55
+ filter<T extends Record<string, unknown>>(input: CorePagingInput = {}): {
56
+ skip: number
57
+ take: number
58
+ where?: T
59
+ orderBy: { [key: string]: 'asc' | 'desc' }
60
+ } {
61
+ const {
62
+ search = '',
63
+ searchFields = [],
64
+ take = 20,
65
+ skip = 0,
66
+ orderBy = 'id',
67
+ orderDirection = 'asc',
68
+ filters = {},
69
+ } = input
70
+
71
+ const trimmedSearch = search.trim()
72
+ const andConditions: unknown[] = []
73
+
74
+ if (Object.keys(filters).length > 0) {
75
+ andConditions.push(filters)
76
+ }
77
+
78
+ if (trimmedSearch && searchFields.length > 0) {
79
+ const terms = trimmedSearch.includes(' ') ? trimmedSearch.split(' ') : [trimmedSearch].filter(Boolean)
80
+ const searchFilters = terms.map((term) => ({
81
+ OR: searchFields.map((field) => ({
82
+ [field]: { contains: term, mode: Prisma.QueryMode.insensitive },
83
+ })),
84
+ }))
85
+ andConditions.push(...searchFilters)
86
+ }
87
+
88
+ const where = andConditions.length > 0 ? { AND: andConditions } : undefined
89
+
90
+ return {
91
+ skip,
92
+ take,
93
+ where: where as unknown as T, // assert type safety for the generic
94
+ orderBy: { [orderBy]: orderDirection },
95
+ }
96
+ }
97
+ }
@@ -0,0 +1,37 @@
1
+ import { Logger } from '@nestjs/common'
2
+ import { RedisPubSub } from 'graphql-redis-subscriptions'
3
+ import Redis from 'ioredis'
4
+
5
+ const REDIS_URL = process.env['REDIS_TLS_URL'] ?? process.env['REDIS_URL'] ?? ''
6
+ const secure = REDIS_URL ? /rediss:/.test(REDIS_URL) : false
7
+ Logger.verbose(`Redis URL: ${REDIS_URL}`)
8
+
9
+ if (!REDIS_URL) {
10
+ Logger.warn('No Redis URL provided. PubSub functionality may be limited.')
11
+ }
12
+
13
+ const options = secure
14
+ ? {
15
+ tls: {
16
+ rejectUnauthorized: false,
17
+ },
18
+ }
19
+ : {}
20
+
21
+ const dateReviver = (_key: unknown, value: string | Date): Date => {
22
+ const isISO8601Z = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/
23
+ if (typeof value === 'string' && isISO8601Z.test(value)) {
24
+ const tempDateNumber = Date.parse(value)
25
+ if (!isNaN(tempDateNumber)) {
26
+ return new Date(tempDateNumber)
27
+ }
28
+ }
29
+ if (typeof value !== 'string') return value
30
+ return new Date(value)
31
+ }
32
+
33
+ export const apiCorePubSub = new RedisPubSub({
34
+ publisher: new Redis(REDIS_URL ?? 'redis://localhost:6379', options),
35
+ subscriber: new Redis(REDIS_URL ?? 'redis://localhost:6379', options),
36
+ reviver: dateReviver,
37
+ })
@@ -0,0 +1,26 @@
1
+ import { Field, InputType } from '@nestjs/graphql'
2
+ import { GraphQLJSONObject } from 'graphql-type-json'
3
+
4
+ @InputType()
5
+ export class CorePagingInput {
6
+ @Field({ nullable: true, defaultValue: 20 })
7
+ take?: number
8
+
9
+ @Field({ nullable: true, defaultValue: 0 })
10
+ skip?: number
11
+
12
+ @Field({ nullable: true })
13
+ search?: string
14
+
15
+ @Field(() => [String], { nullable: true })
16
+ searchFields?: string[]
17
+
18
+ @Field({ nullable: true })
19
+ orderDirection?: 'asc' | 'desc'
20
+
21
+ @Field({ nullable: true })
22
+ orderBy?: string
23
+
24
+ @Field(() => GraphQLJSONObject, { nullable: true })
25
+ filters?: Record<string, unknown>
26
+ }
@@ -0,0 +1,7 @@
1
+ import { Field, InputType } from '@nestjs/graphql'
2
+
3
+ @InputType()
4
+ export class MultiSelectInput {
5
+ @Field({ nullable: true })
6
+ id?: string
7
+ }
@@ -0,0 +1,19 @@
1
+ import { Field, ObjectType } from '@nestjs/graphql'
2
+
3
+ @ObjectType()
4
+ export class CorePaging {
5
+ @Field({ nullable: true })
6
+ total?: number
7
+
8
+ @Field({ nullable: true })
9
+ count?: number
10
+
11
+ @Field({ nullable: true })
12
+ take?: number
13
+
14
+ @Field({ nullable: true })
15
+ page?: number
16
+
17
+ @Field({ nullable: true })
18
+ skip?: number
19
+ }
@@ -0,0 +1,2 @@
1
+ export * from './lib/api-core-feature.module'
2
+ export * from './lib/api-core-feature.service'
@@ -0,0 +1,12 @@
1
+ import { Controller, Get } from '@nestjs/common'
2
+ import { ApiCoreFeatureService } from './api-core-feature.service'
3
+
4
+ @Controller()
5
+ export class ApiCoreFeatureController {
6
+ constructor(private readonly service: ApiCoreFeatureService) {}
7
+
8
+ @Get('uptime')
9
+ uptime() {
10
+ return this.service.uptime()
11
+ }
12
+ }
@@ -0,0 +1,86 @@
1
+ import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo'
2
+ import { Module } from '@nestjs/common'
3
+ import { ConfigModule } from '@nestjs/config'
4
+ import { GraphQLModule } from '@nestjs/graphql'
5
+ import { join } from 'path'
6
+ import { Request, Response } from 'express'
7
+ import { apiCorePubSub } from '@<%= npmScope %>/api/core/data-access'
8
+ import { configuration, validationSchema } from '@<%= npmScope %>/api/config'
9
+ import { Context } from 'graphql-ws'
10
+ import { ApiCoreFeatureController } from './api-core-feature.controller'
11
+ import { ApiCoreFeatureResolver } from './api-core-feature.resolver'
12
+ import { ApiCoreFeatureService } from './api-core-feature.service'
13
+ import { ComplexityPlugin } from './plugins/complexity.plugin'
14
+
15
+ interface ConnectionParameters {
16
+ headers?: Record<string, string>
17
+ }
18
+
19
+ const redisPubSubProvider = {
20
+ provide: 'REDIS_PUB_SUB',
21
+ useValue: apiCorePubSub,
22
+ }
23
+
24
+ @Module({
25
+ imports: [
26
+ ConfigModule.forRoot({
27
+ isGlobal: true,
28
+ load: [configuration],
29
+ validationSchema,
30
+ }),
31
+ GraphQLModule.forRoot<ApolloDriverConfig>({
32
+ driver: ApolloDriver,
33
+ autoSchemaFile: join(process.cwd(), 'api-schema.graphql'),
34
+ subscriptions: {
35
+ 'graphql-ws': {
36
+ onConnect: async (context: Context<Record<string, unknown> | undefined>) => {
37
+ const { extra } = context
38
+ if (extra && typeof extra === 'object' && 'request' in extra && extra.request && typeof extra.request === 'object' && 'rawHeaders' in extra.request) {
39
+ const rawHeaders = extra.request.rawHeaders as string[] | undefined
40
+ let token = ''
41
+ if (rawHeaders) {
42
+ for (let i = 0; i < rawHeaders.length; i += 2) {
43
+ if (rawHeaders[i].toLowerCase() === 'cookie') {
44
+ const cookies = rawHeaders[i + 1].split(';')
45
+ for (const cookie of cookies) {
46
+ const [name, value] = cookie.trim().split('=')
47
+ if (name === '__session') {
48
+ token = value
49
+ break
50
+ }
51
+ }
52
+ break
53
+ }
54
+ }
55
+ }
56
+
57
+ if (token === '') {
58
+ throw new Error('Authentication token is missing')
59
+ }
60
+ } else {
61
+ throw new Error('Authentication token is missing')
62
+ }
63
+ return true;
64
+ },
65
+ },
66
+ },
67
+ context: ({ req, res, connectionParams }: { req: Partial<Request>; res: Response; connectionParams: ConnectionParameters }) => {
68
+ if (connectionParams) {
69
+ req = { headers: connectionParams.headers }
70
+ }
71
+ return { req, res }
72
+ },
73
+ sortSchema: true,
74
+ buildSchemaOptions: {
75
+ dateScalarMode: 'isoDate', // Better interoperability (ISO strings, not JS Dates)
76
+ numberScalarMode: 'float', // Default is fine; override to 'integer' if you hate floats
77
+ scalarsMap: [], // Optional for custom scalar mappings if you use them
78
+ skipCheck: true, // Skip extra metadata validation — speeds up build
79
+ },
80
+ }),
81
+ ],
82
+ controllers: [ApiCoreFeatureController],
83
+ providers: [ApiCoreFeatureResolver, ApiCoreFeatureService, ComplexityPlugin, redisPubSubProvider],
84
+ exports: [ApiCoreFeatureService, ComplexityPlugin, 'REDIS_PUB_SUB'],
85
+ })
86
+ export class ApiCoreFeatureModule {}
@@ -0,0 +1,12 @@
1
+ import { Float, Query, Resolver } from '@nestjs/graphql'
2
+ import { ApiCoreFeatureService } from './api-core-feature.service'
3
+
4
+ @Resolver()
5
+ export class ApiCoreFeatureResolver {
6
+ constructor(private readonly service: ApiCoreFeatureService) {}
7
+
8
+ @Query(() => Float, { nullable: true })
9
+ uptime() {
10
+ return this.service.uptime()
11
+ }
12
+ }
@@ -0,0 +1,55 @@
1
+ import { Injectable } from '@nestjs/common'
2
+ import { ConfigService } from '@nestjs/config'
3
+ import { CookieOptions } from 'express'
4
+
5
+ @Injectable()
6
+ export class ApiCoreFeatureService {
7
+ constructor(public readonly config: ConfigService) {}
8
+
9
+ uptime(): number {
10
+ return process.uptime()
11
+ }
12
+
13
+ get apiUrl(): string {
14
+ return this.config.getOrThrow<string>('apiUrl')
15
+ }
16
+
17
+ get apiCorsOrigins(): string[] {
18
+ return this.config.get<string[]>('api.cors.origin') ?? []
19
+ }
20
+
21
+ get cookie(): { name: string; options: CookieOptions } {
22
+ return this.config.getOrThrow<{ name: string; options: CookieOptions }>('api.cookie')
23
+ }
24
+
25
+ get appEmail(): string {
26
+ return this.config.getOrThrow<string>('app.email')
27
+ }
28
+
29
+ get appSupportEmail(): string {
30
+ return this.config.getOrThrow<string>('app.supportEmail')
31
+ }
32
+
33
+ get appAdminEmails(): string {
34
+ return this.config.getOrThrow<string>('app.adminEmails')
35
+ }
36
+
37
+ get appName(): string {
38
+ return this.config.getOrThrow<string>('app.name')
39
+ }
40
+
41
+ get siteUrl(): string {
42
+ return this.config.getOrThrow<string>('siteUrl')
43
+ }
44
+
45
+ get mailerConfig() {
46
+ return {
47
+ host: this.config.getOrThrow<string>('smtp.host'),
48
+ port: this.config.getOrThrow<string>('smtp.port'),
49
+ auth: {
50
+ user: this.config.getOrThrow<string>('smtp.user'),
51
+ pass: this.config.getOrThrow<string>('smtp.pass'),
52
+ },
53
+ }
54
+ }
55
+ }
@@ -0,0 +1,32 @@
1
+ export const configuration = () => ({
2
+ prefix: 'api',
3
+ environment: process.env['NODE_ENV'],
4
+ host: process.env['HOST'],
5
+ port: parseInt(process.env['PORT'] ?? '3000', 10),
6
+ apiUrl: process.env['API_URL'],
7
+ api: {
8
+ cookie: {
9
+ name: process.env['API_COOKIE_NAME'],
10
+ options: {
11
+ domain: process.env['API_COOKIE_DOMAIN'],
12
+ httpOnly: true,
13
+ },
14
+ },
15
+ cors: {
16
+ origin: [process.env['WEB_URL']],
17
+ },
18
+ },
19
+ siteUrl: process.env['SITE_URL'] || process.env['API_URL']?.replace('/api', ''),
20
+ app: {
21
+ email: process.env['APP_EMAIL'],
22
+ supportEmail: process.env['APP_SUPPORT_EMAIL'],
23
+ adminEmails: process.env['APP_ADMIN_EMAILS'],
24
+ name: process.env['APP_NAME'],
25
+ },
26
+ smtp: {
27
+ host: process.env['SMTP_HOST'],
28
+ port: process.env['SMTP_PORT'],
29
+ user: process.env['SMTP_USER'],
30
+ pass: process.env['SMTP_PASS'],
31
+ },
32
+ })
@@ -0,0 +1,25 @@
1
+ import * as Joi from 'joi'
2
+
3
+ export const validationSchema = Joi.object({
4
+ NODE_ENV: Joi.string().valid('development', 'production', 'test'),
5
+ HOST: Joi.string().alphanum().default('localhost'),
6
+ PORT: Joi.number().default(3000),
7
+ WEB_PORT: Joi.number().default(4200),
8
+ WEB_URL: Joi.string().default(
9
+ `http://${process.env['HOST'] || 'localhost'}:${process.env['WEB_PORT']}`
10
+ ),
11
+ API_COOKIE_DOMAIN: Joi.string().default('localhost'),
12
+ API_COOKIE_NAME: Joi.string().default('__session'),
13
+ API_URL: Joi.string().default(
14
+ `http://${process.env['HOST'] || 'localhost'}:${process.env['PORT']}/api`
15
+ ),
16
+ APP_NAME: Joi.string().required(),
17
+ APP_EMAIL: Joi.string().email().required(),
18
+ APP_SUPPORT_EMAIL: Joi.string().email().required(),
19
+ APP_ADMIN_EMAILS: Joi.string().required(),
20
+ SITE_URL: Joi.string().uri().required(),
21
+ SMTP_HOST: Joi.string().required(),
22
+ SMTP_PORT: Joi.string().required(),
23
+ SMTP_USER: Joi.string().required(),
24
+ SMTP_PASS: Joi.string().required(),
25
+ })
@@ -0,0 +1,51 @@
1
+ import { GraphQLSchemaHost } from '@nestjs/graphql'
2
+ import { Plugin } from '@nestjs/apollo'
3
+ import { ApolloServerPlugin, GraphQLRequestListener } from '@apollo/server'
4
+ import { GraphQLError } from 'graphql'
5
+ import { fieldExtensionsEstimator, getComplexity, simpleEstimator } from 'graphql-query-complexity'
6
+ import { Logger } from '@nestjs/common'
7
+
8
+ @Plugin()
9
+ export class ComplexityPlugin implements ApolloServerPlugin {
10
+ constructor(private gqlSchemaHost: GraphQLSchemaHost) {}
11
+
12
+ async requestDidStart(): Promise<void | GraphQLRequestListener<any>> {
13
+ const maxComplexity = process.env['QUERY_COMPLEXITY_LIMIT'] ? parseInt(process.env['QUERY_COMPLEXITY_LIMIT']) : 15000
14
+ const logQueryComplexity = process.env['LOG_QUERY_COMPLEXITY'] ? process.env['LOG_QUERY_COMPLEXITY'] === 'true' : false
15
+ const logQueryComplexityThreshold = process.env['LOG_QUERY_COMPLEXITY_THRESHOLD']
16
+ ? parseInt(process.env['LOG_QUERY_COMPLEXITY_THRESHOLD'])
17
+ : 5000
18
+ const queryComplexityVerboseErrors = process.env['QUERY_COMPLEXITY_VERBOSE_ERRORS']
19
+ ? process.env['QUERY_COMPLEXITY_VERBOSE_ERRORS'] === 'true'
20
+ : false
21
+
22
+ const { schema } = this.gqlSchemaHost
23
+
24
+ return {
25
+ didResolveOperation: async ({ request, document }): Promise<void> => {
26
+ const complexity = getComplexity({
27
+ schema,
28
+ operationName: request.operationName,
29
+ query: document,
30
+ variables: request.variables,
31
+ estimators: [fieldExtensionsEstimator(), simpleEstimator({ defaultComplexity: 1 })],
32
+ })
33
+
34
+ if (logQueryComplexity && complexity > logQueryComplexityThreshold) {
35
+ Logger.log(`Query Complexity for "${request.operationName}": ${complexity}`)
36
+ }
37
+
38
+ if (complexity > maxComplexity) {
39
+ let message = `Query "${request.operationName}" is too complex: ${complexity}. Maximum allowed complexity: ${maxComplexity}.`
40
+ if (queryComplexityVerboseErrors) {
41
+ message = `${message}\n\nVariables:\n${JSON.stringify(request.variables)}\n\nFull Query:\n${request.query}`
42
+ }
43
+ Logger.error(message)
44
+ throw new GraphQLError(message)
45
+ }
46
+
47
+ return Promise.resolve()
48
+ },
49
+ }
50
+ }
51
+ }
@@ -0,0 +1,17 @@
1
+ import { Plugin } from '@nestjs/apollo'
2
+ import { ApolloServerPlugin, GraphQLRequestListener } from '@apollo/server'
3
+
4
+ /**
5
+ * This is a general logging plugin that is a placeholder for future plugin use.
6
+ */
7
+ @Plugin()
8
+ export class LoggingPlugin implements ApolloServerPlugin {
9
+ async requestDidStart(): Promise<GraphQLRequestListener<any>> {
10
+ console.log('Request started')
11
+ return {
12
+ async willSendResponse(): Promise<void> {
13
+ console.log('Will send response')
14
+ },
15
+ }
16
+ }
17
+ }
@@ -0,0 +1 @@
1
+ export * from './lib/models'