@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.
- package/CHANGELOG.md +5 -0
- package/README.md +11 -0
- package/eslint.config.cjs +28 -0
- package/generators.json +69 -0
- package/package.json +21 -0
- package/project.json +47 -0
- package/src/account/files/data-access/src/index.ts__tmpl__ +5 -0
- package/src/account/files/data-access/src/lib/api-account-data-access.module.ts__tmpl__ +10 -0
- package/src/account/files/data-access/src/lib/api-account-data-access.service.ts__tmpl__ +152 -0
- package/src/account/files/data-access/src/lib/dto/account-create-email.input.ts__tmpl__ +9 -0
- package/src/account/files/data-access/src/lib/dto/account-update-password.input.ts__tmpl__ +16 -0
- package/src/account/files/data-access/src/lib/dto/account-update-profile.input.ts__tmpl__ +25 -0
- package/src/account/files/feature/src/index.ts__tmpl__ +1 -0
- package/src/account/files/feature/src/lib/api-account-feature.module.ts__tmpl__ +9 -0
- package/src/account/files/feature/src/lib/api-account-feature.resolver.ts__tmpl__ +83 -0
- package/src/account/generator.spec.ts +71 -0
- package/src/account/generator.ts +20 -0
- package/src/account/schema.d.ts +3 -0
- package/src/account/schema.json +13 -0
- package/src/app/files/src/app.config.ts__tmpl__ +66 -0
- package/src/app/files/src/app.module.ts__tmpl__ +43 -0
- package/src/app/files/src/applogger.middleware.ts__tmpl__ +21 -0
- package/src/app/files/src/main.ts__tmpl__ +33 -0
- package/src/app/files/webpack.config.js__tmpl__ +54 -0
- package/src/app/generator.spec.ts +112 -0
- package/src/app/generator.ts +105 -0
- package/src/app/schema.d.ts +1 -0
- package/src/app/schema.json +9 -0
- package/src/config/files/src/index.ts__tmpl__ +3 -0
- package/src/config/files/src/lib/config.service.ts__tmpl__ +51 -0
- package/src/config/files/src/lib/configuration.ts__tmpl__ +32 -0
- package/src/config/files/src/lib/validation.ts__tmpl__ +21 -0
- package/src/config/generator.spec.ts +47 -0
- package/src/config/generator.ts +16 -0
- package/src/config/schema.d.ts +3 -0
- package/src/config/schema.json +13 -0
- package/src/core/files/data-access/src/index.ts__tmpl__ +5 -0
- package/src/core/files/data-access/src/lib/api-core-data-access.module.ts__tmpl__ +9 -0
- package/src/core/files/data-access/src/lib/api-core-data-access.service.ts__tmpl__ +97 -0
- package/src/core/files/data-access/src/lib/api-core-pub-sub.ts__tmpl__ +37 -0
- package/src/core/files/data-access/src/lib/dto/core-paging.input.ts__tmpl__ +26 -0
- package/src/core/files/data-access/src/lib/dto/multi-select-input.ts__tmpl__ +7 -0
- package/src/core/files/data-access/src/lib/models/core-paging.ts__tmpl__ +19 -0
- package/src/core/files/feature/src/index.ts__tmpl__ +2 -0
- package/src/core/files/feature/src/lib/api-core-feature.controller.ts__tmpl__ +12 -0
- package/src/core/files/feature/src/lib/api-core-feature.module.ts__tmpl__ +86 -0
- package/src/core/files/feature/src/lib/api-core-feature.resolver.ts__tmpl__ +12 -0
- package/src/core/files/feature/src/lib/api-core-feature.service.ts__tmpl__ +55 -0
- package/src/core/files/feature/src/lib/config/configuration.ts__tmpl__ +32 -0
- package/src/core/files/feature/src/lib/config/validation.ts__tmpl__ +25 -0
- package/src/core/files/feature/src/lib/plugins/complexity.plugin.ts__tmpl__ +51 -0
- package/src/core/files/feature/src/lib/plugins/logging.plugin.ts__tmpl__ +17 -0
- package/src/core/files/models/src/index.ts__tmpl__ +1 -0
- package/src/core/files/models/src/lib/generate-models.ts__tmpl__ +294 -0
- package/src/core/files/models/src/lib/models/core-paging.model.ts__tmpl__ +25 -0
- package/src/core/generator.spec.ts +85 -0
- package/src/core/generator.ts +35 -0
- package/src/core/schema.d.ts +3 -0
- package/src/core/schema.json +13 -0
- package/src/custom/generator.spec.ts +75 -0
- package/src/custom/generator.ts +239 -0
- package/src/custom/schema.json +21 -0
- package/src/custom/schema.ts +5 -0
- package/src/extended/generator.spec.ts +95 -0
- package/src/extended/generator.ts +161 -0
- package/src/extended/index.ts +1 -0
- package/src/extended/schema.json +12 -0
- package/src/extended/schema.ts +3 -0
- package/src/generate-crud/files/data-access/src/index.ts__tmpl__ +3 -0
- package/src/generate-crud/files/data-access/src/lib/api-crud-data-access.module.ts__tmpl__ +11 -0
- package/src/generate-crud/files/data-access/src/lib/api-crud-data-access.service.ts__tmpl__ +72 -0
- package/src/generate-crud/files/data-access/src/lib/dto/index.ts__tmpl__ +224 -0
- package/src/generate-crud/files/feature/.gitkeep +0 -0
- package/src/generate-crud/generator.spec.ts +84 -0
- package/src/generate-crud/generator.ts +354 -0
- package/src/generate-crud/schema.json +32 -0
- package/src/generate-crud/schema.ts +8 -0
- package/src/index.ts +13 -0
- package/src/plugin/generator.spec.ts +18 -0
- package/src/plugin/generator.ts +74 -0
- package/src/plugin/schema.json +14 -0
- package/src/plugin/schema.ts +4 -0
- package/src/prisma/files/src/index.ts__tmpl__ +1 -0
- package/src/prisma/files/src/lib/.gitkeep +1 -0
- package/src/prisma/files/src/lib/schemas/schema.prisma__tmpl__ +402 -0
- package/src/prisma/files/src/lib/seed/seed-data/iso-3166-countries.ts__tmpl__ +3239 -0
- package/src/prisma/files/src/lib/seed/seed-data/seed-users.ts__tmpl__ +32 -0
- package/src/prisma/files/src/lib/seed/seed.ts__tmpl__ +64 -0
- package/src/prisma/generator.spec.ts +60 -0
- package/src/prisma/generator.ts +61 -0
- package/src/prisma/schema.d.ts +3 -0
- package/src/prisma/schema.json +13 -0
- package/src/setup/generator.md +49 -0
- package/src/setup/generator.spec.ts +18 -0
- package/src/setup/generator.ts +106 -0
- package/src/setup/schema.json +8 -0
- package/src/smtp-mailer/files/data-access/src/index.ts__tmpl__ +2 -0
- package/src/smtp-mailer/files/data-access/src/lib/api-smtp-mailer-data-access.module.ts__tmpl__ +10 -0
- package/src/smtp-mailer/files/data-access/src/lib/api-smtp-mailer-data-access.service.ts__tmpl__ +61 -0
- package/src/smtp-mailer/generator.spec.ts +41 -0
- package/src/smtp-mailer/generator.ts +14 -0
- package/src/smtp-mailer/schema.d.ts +0 -0
- package/src/smtp-mailer/schema.json +7 -0
- package/src/user/files/data-access/src/index.ts__tmpl__ +5 -0
- package/src/user/files/data-access/src/lib/api-user-data-access.module.ts__tmpl__ +10 -0
- package/src/user/files/data-access/src/lib/api-user-data-access.service.ts__tmpl__ +119 -0
- package/src/user/files/data-access/src/lib/dto/admin-create-user.input.ts__tmpl__ +20 -0
- package/src/user/files/data-access/src/lib/dto/admin-update-user.input.ts__tmpl__ +29 -0
- package/src/user/files/feature/src/index.ts__tmpl__ +1 -0
- package/src/user/files/feature/src/lib/api-user-feature-admin.resolver.ts__tmpl__ +57 -0
- package/src/user/files/feature/src/lib/api-user-feature.module.ts__tmpl__ +10 -0
- package/src/user/files/feature/src/lib/api-user-feature.resolver.ts__tmpl__ +17 -0
- package/src/user/generator.spec.ts +41 -0
- package/src/user/generator.ts +15 -0
- package/src/user/schema.d.ts +0 -0
- package/src/user/schema.json +7 -0
- package/src/utils/files/src/index.ts__tmpl__ +3 -0
- package/src/utils/files/src/lib/decorators/ctx-user.decorator.ts__tmpl__ +6 -0
- package/src/utils/files/src/lib/guards/gql-auth-admin.guard.ts__tmpl__ +39 -0
- package/src/utils/files/src/lib/guards/gql-auth.guard.ts__tmpl__ +11 -0
- package/src/utils/generator.ts +14 -0
- package/src/utils/schema.json +8 -0
- package/src/workspace-setup/generator.md +39 -0
- package/src/workspace-setup/generator.spec.ts +82 -0
- package/src/workspace-setup/generator.ts +49 -0
- package/src/workspace-setup/lib/helpers.ts +142 -0
- package/src/workspace-setup/schema.d.ts +3 -0
- package/src/workspace-setup/schema.json +7 -0
- package/tsconfig.json +16 -0
- package/tsconfig.lib.json +23 -0
- package/tsconfig.spec.json +22 -0
- package/vite.config.mts +37 -0
package/CHANGELOG.md
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const baseConfig = require('../../eslint.config.js')
|
|
2
|
+
|
|
3
|
+
module.exports = [
|
|
4
|
+
...baseConfig,
|
|
5
|
+
{
|
|
6
|
+
files: ['**/*.json'],
|
|
7
|
+
rules: {
|
|
8
|
+
'@nx/dependency-checks': [
|
|
9
|
+
'error',
|
|
10
|
+
{
|
|
11
|
+
ignoredFiles: ['{projectRoot}/eslint.config.{js,cjs,mjs}', '{projectRoot}/vite.config.{js,ts,mjs,mts}'],
|
|
12
|
+
},
|
|
13
|
+
],
|
|
14
|
+
},
|
|
15
|
+
languageOptions: {
|
|
16
|
+
parser: require('jsonc-eslint-parser'),
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
files: ['**/package.json'],
|
|
21
|
+
rules: {
|
|
22
|
+
'@nx/nx-plugin-checks': 'error',
|
|
23
|
+
},
|
|
24
|
+
languageOptions: {
|
|
25
|
+
parser: require('jsonc-eslint-parser'),
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
]
|
package/generators.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"generators": {
|
|
3
|
+
"setup": {
|
|
4
|
+
"factory": "./src/setup/generator.js",
|
|
5
|
+
"schema": "./src/setup/schema.json",
|
|
6
|
+
"description": "Set up all dependencies for the API"
|
|
7
|
+
},
|
|
8
|
+
"app": {
|
|
9
|
+
"factory": "./src/app/generator.js",
|
|
10
|
+
"schema": "./src/app/schema.json",
|
|
11
|
+
"description": "Set up main Nest.js application"
|
|
12
|
+
},
|
|
13
|
+
"prisma": {
|
|
14
|
+
"factory": "./src/prisma/generator.js",
|
|
15
|
+
"schema": "./src/prisma/schema.json",
|
|
16
|
+
"description": "Create the API Prisma library"
|
|
17
|
+
},
|
|
18
|
+
"config": {
|
|
19
|
+
"factory": "./src/config/generator.js",
|
|
20
|
+
"schema": "./src/config/schema.json",
|
|
21
|
+
"description": "Create the API Config library"
|
|
22
|
+
},
|
|
23
|
+
"account": {
|
|
24
|
+
"factory": "./src/account/generator.js",
|
|
25
|
+
"schema": "./src/account/schema.json",
|
|
26
|
+
"description": "Create an API Account Library"
|
|
27
|
+
},
|
|
28
|
+
"core": {
|
|
29
|
+
"factory": "./src/core/generator.js",
|
|
30
|
+
"schema": "./src/core/schema.json",
|
|
31
|
+
"description": "Create the API Core library"
|
|
32
|
+
},
|
|
33
|
+
"custom": {
|
|
34
|
+
"factory": "./src/custom/generator.js",
|
|
35
|
+
"schema": "./src/custom/schema.json",
|
|
36
|
+
"description": "Generate custom API libraries based on Prisma models"
|
|
37
|
+
},
|
|
38
|
+
"generate-crud": {
|
|
39
|
+
"factory": "./src/generate-crud/generator.js",
|
|
40
|
+
"schema": "./src/generate-crud/schema.json",
|
|
41
|
+
"description": "Generate CRUD libraries based on Prisma models"
|
|
42
|
+
},
|
|
43
|
+
"smtp-mailer": {
|
|
44
|
+
"factory": "./src/smtp-mailer/generator.js",
|
|
45
|
+
"schema": "./src/smtp-mailer/schema.json",
|
|
46
|
+
"description": "Create an API Mailer Library"
|
|
47
|
+
},
|
|
48
|
+
"user": {
|
|
49
|
+
"factory": "./src/user/generator.js",
|
|
50
|
+
"schema": "./src/user/schema.json",
|
|
51
|
+
"description": "Create an API User Library"
|
|
52
|
+
},
|
|
53
|
+
"workspace-setup": {
|
|
54
|
+
"factory": "./src/workspace-setup/generator.js",
|
|
55
|
+
"schema": "./src/workspace-setup/schema.json",
|
|
56
|
+
"description": "Set up the workspace environment"
|
|
57
|
+
},
|
|
58
|
+
"plugin": {
|
|
59
|
+
"factory": "./src/plugin/generator.js",
|
|
60
|
+
"schema": "./src/plugin/schema.json",
|
|
61
|
+
"description": "Generate a blank plugin (module, service, resolver) in the custom library's plugins folder."
|
|
62
|
+
},
|
|
63
|
+
"utils": {
|
|
64
|
+
"factory": "./src/utils/generator.js",
|
|
65
|
+
"schema": "./src/utils/schema.json",
|
|
66
|
+
"description": "Generate a utility plugin with auth utils."
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nestledjs/api",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"generators": "./generators.json",
|
|
5
|
+
"type": "commonjs",
|
|
6
|
+
"main": "./src/index.js",
|
|
7
|
+
"types": "./src/index.d.ts",
|
|
8
|
+
"description": "API generators for Nestled projects",
|
|
9
|
+
"author": "Nestled Contributors",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "https://github.com/nestledjs/nestled"
|
|
14
|
+
},
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"tslib": "^2.3.0"
|
|
20
|
+
}
|
|
21
|
+
}
|
package/project.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "api",
|
|
3
|
+
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
|
4
|
+
"sourceRoot": "generators/api/src",
|
|
5
|
+
"projectType": "library",
|
|
6
|
+
"tags": [],
|
|
7
|
+
"publishable": true,
|
|
8
|
+
"targets": {
|
|
9
|
+
"build": {
|
|
10
|
+
"executor": "@nx/js:tsc",
|
|
11
|
+
"outputs": ["{options.outputPath}"],
|
|
12
|
+
"options": {
|
|
13
|
+
"outputPath": "dist/generators/api",
|
|
14
|
+
"main": "generators/api/src/index.ts",
|
|
15
|
+
"tsConfig": "generators/api/tsconfig.lib.json",
|
|
16
|
+
"assets": [
|
|
17
|
+
"generators/api/*.md",
|
|
18
|
+
{
|
|
19
|
+
"input": "./generators/api/src",
|
|
20
|
+
"glob": "**/!(*.ts)",
|
|
21
|
+
"output": "./src"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"input": "./generators/api/src",
|
|
25
|
+
"glob": "**/*.d.ts",
|
|
26
|
+
"output": "./src"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"input": "./generators/api",
|
|
30
|
+
"glob": "generators.json",
|
|
31
|
+
"output": "."
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"input": "./generators/api",
|
|
35
|
+
"glob": "executors.json",
|
|
36
|
+
"output": "."
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"input": "./generators/api/src",
|
|
40
|
+
"glob": "*/files/**/*",
|
|
41
|
+
"output": "./src"
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export * from './lib/api-account-data-access.module'
|
|
2
|
+
export * from './lib/api-account-data-access.service'
|
|
3
|
+
export * from './lib/dto/account-create-email.input'
|
|
4
|
+
export * from './lib/dto/account-update-password.input'
|
|
5
|
+
export * from './lib/dto/account-update-profile.input'
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Module } from '@nestjs/common'
|
|
2
|
+
import { ApiCoreDataAccessModule } from '@<%= npmScope %>/api/core/data-access'
|
|
3
|
+
import { ApiAccountDataAccessService } from './api-account-data-access.service'
|
|
4
|
+
|
|
5
|
+
@Module({
|
|
6
|
+
imports: [ApiCoreDataAccessModule],
|
|
7
|
+
providers: [ApiAccountDataAccessService],
|
|
8
|
+
exports: [ApiAccountDataAccessService],
|
|
9
|
+
})
|
|
10
|
+
export class ApiAccountDataAccessModule {}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BadRequestException,
|
|
3
|
+
Injectable,
|
|
4
|
+
Logger,
|
|
5
|
+
UnauthorizedException,
|
|
6
|
+
} from '@nestjs/common'
|
|
7
|
+
import { ApiCoreDataAccessService, Prisma } from '@<%= npmScope %>/api/core/data-access'
|
|
8
|
+
import {
|
|
9
|
+
hashPassword,
|
|
10
|
+
validatePassword,
|
|
11
|
+
} from '@<%= npmScope %>/api/auth/data-access'
|
|
12
|
+
import { AccountCreateEmailInput } from './dto/account-create-email.input'
|
|
13
|
+
import { AccountUpdatePasswordInput } from './dto/account-update-password.input'
|
|
14
|
+
import { AccountUpdateProfileInput } from './dto/account-update-profile.input'
|
|
15
|
+
|
|
16
|
+
@Injectable()
|
|
17
|
+
export class ApiAccountDataAccessService {
|
|
18
|
+
constructor(private readonly data: ApiCoreDataAccessService) {}
|
|
19
|
+
|
|
20
|
+
accountEmails(userId: string) {
|
|
21
|
+
return this.data.email.findMany({
|
|
22
|
+
where: { userId },
|
|
23
|
+
orderBy: { primary: 'desc' },
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
accountProfile(userId: string) {
|
|
28
|
+
return this.data.findUserById(userId)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async accountCreateEmail(userId: string, input: AccountCreateEmailInput) {
|
|
32
|
+
const exists = await this.data.findUserByEmail(input.email)
|
|
33
|
+
if (exists) {
|
|
34
|
+
throw new BadRequestException(`Can't add email address ${input.email}`)
|
|
35
|
+
}
|
|
36
|
+
return this.data.email.create({
|
|
37
|
+
data: { userId, email: input.email, primary: false },
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async accountDeleteEmail(userId: string, userEmailId: string) {
|
|
42
|
+
const email = await this.data.email.findUnique({
|
|
43
|
+
where: { id: userEmailId },
|
|
44
|
+
include: { user: true },
|
|
45
|
+
})
|
|
46
|
+
if (!email || !email.user) {
|
|
47
|
+
throw new BadRequestException(`Email not found`)
|
|
48
|
+
}
|
|
49
|
+
if (email.user.id !== userId) {
|
|
50
|
+
throw new UnauthorizedException()
|
|
51
|
+
}
|
|
52
|
+
if (email.primary) {
|
|
53
|
+
throw new BadRequestException(`You can't delete your primary email`)
|
|
54
|
+
}
|
|
55
|
+
return this.data.email.delete({ where: { id: userEmailId } })
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
accountUpdateProfile(userId: string, input: AccountUpdateProfileInput) {
|
|
59
|
+
return this.data.user.update({
|
|
60
|
+
where: { id: userId },
|
|
61
|
+
data: {
|
|
62
|
+
firstName: input.firstName,
|
|
63
|
+
lastName: input.lastName,
|
|
64
|
+
bio: input.bio,
|
|
65
|
+
},
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async accountUpdateDisplayName(userId: string, displayName: string) {
|
|
70
|
+
const available = await this.accountDisplayNameAvailable(userId, displayName)
|
|
71
|
+
|
|
72
|
+
if (!available) {
|
|
73
|
+
throw new BadRequestException(`Display name ${displayName} is not available`)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return this.data.user.update({ where: { id: userId }, data: { displayName } })
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async accountDisplayNameAvailable(userId: string, displayName: string): Promise<boolean> {
|
|
80
|
+
const denied = await this.data.displayNameDenyList(displayName)
|
|
81
|
+
|
|
82
|
+
if (denied) {
|
|
83
|
+
throw new BadRequestException(`Display name ${displayName} is not available`)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const count = await this.data.user.count({
|
|
87
|
+
where: { displayName },
|
|
88
|
+
})
|
|
89
|
+
return count === 0
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async userEmailOwner(userId: string, userEmailId: string) {
|
|
93
|
+
const email = await this.data.email.findUnique({
|
|
94
|
+
where: { id: userEmailId },
|
|
95
|
+
include: { user: true }
|
|
96
|
+
})
|
|
97
|
+
if (!email || !email.user) {
|
|
98
|
+
throw new BadRequestException(`Email not found`)
|
|
99
|
+
}
|
|
100
|
+
if (email.user.id !== userId) {
|
|
101
|
+
throw new UnauthorizedException()
|
|
102
|
+
}
|
|
103
|
+
return email.user
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async updateUserEmail(userId: string, userEmailId: string, input: Prisma.EmailUpdateInput) {
|
|
107
|
+
await this.userEmailOwner(userId, userEmailId)
|
|
108
|
+
return this.data.email.update({ where: { id: userEmailId }, data: { ...input } })
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async accountMarkEmailPrivate(userId: string, userEmailId: string) {
|
|
112
|
+
return this.updateUserEmail(userId, userEmailId, { public: false })
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async accountMarkEmailPublic(userId: string, userEmailId: string) {
|
|
116
|
+
return this.updateUserEmail(userId, userEmailId, { public: true })
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async accountMarkEmailPrimary(userId: string, userEmailId: string) {
|
|
120
|
+
await this.userEmailOwner(userId, userEmailId)
|
|
121
|
+
await this.data.email.updateMany({
|
|
122
|
+
where: { userId },
|
|
123
|
+
data: { primary: false },
|
|
124
|
+
})
|
|
125
|
+
return this.updateUserEmail(userId, userEmailId, { primary: true })
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async accountResetPassword(userId: string) {
|
|
129
|
+
const emails = await this.accountEmails(userId)
|
|
130
|
+
const primary = emails.find((email) => email.primary)
|
|
131
|
+
|
|
132
|
+
if (!primary) {
|
|
133
|
+
throw new BadRequestException(`Could not find primary email for user with id ${userId}`)
|
|
134
|
+
}
|
|
135
|
+
Logger.verbose(`TODO: Implement Account Reset Password`)
|
|
136
|
+
return true
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async accountUpdatePassword(userId: string, input: AccountUpdatePasswordInput) {
|
|
140
|
+
const user = await this.data.findUserById(userId)
|
|
141
|
+
if (!user || !user.password) {
|
|
142
|
+
throw new BadRequestException(`User not found or password not set`)
|
|
143
|
+
}
|
|
144
|
+
await validatePassword(input.currentPassword, user.password)
|
|
145
|
+
|
|
146
|
+
if (input.password !== input.verified) {
|
|
147
|
+
throw new BadRequestException(`Passwords do not match!`)
|
|
148
|
+
}
|
|
149
|
+
await this.data.user.update({ where: { id: userId }, data: { password: hashPassword(input.password) } })
|
|
150
|
+
return true
|
|
151
|
+
}
|
|
152
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Field, InputType } from '@nestjs/graphql'
|
|
2
|
+
import { MinLength } from 'class-validator'
|
|
3
|
+
|
|
4
|
+
@InputType()
|
|
5
|
+
export class AccountUpdatePasswordInput {
|
|
6
|
+
@Field()
|
|
7
|
+
currentPassword?: string
|
|
8
|
+
|
|
9
|
+
@Field()
|
|
10
|
+
@MinLength(10)
|
|
11
|
+
password?: string
|
|
12
|
+
|
|
13
|
+
@Field()
|
|
14
|
+
@MinLength(10)
|
|
15
|
+
verified?: string
|
|
16
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Field, InputType } from '@nestjs/graphql'
|
|
2
|
+
|
|
3
|
+
@InputType()
|
|
4
|
+
export class AccountUpdateProfileInput {
|
|
5
|
+
@Field({ nullable: true })
|
|
6
|
+
firstName?: string
|
|
7
|
+
|
|
8
|
+
@Field({ nullable: true })
|
|
9
|
+
lastName?: string
|
|
10
|
+
|
|
11
|
+
@Field({ nullable: true })
|
|
12
|
+
avatarUrl?: string
|
|
13
|
+
|
|
14
|
+
@Field({ nullable: true })
|
|
15
|
+
bio?: string
|
|
16
|
+
|
|
17
|
+
@Field({ nullable: true })
|
|
18
|
+
location?: string
|
|
19
|
+
|
|
20
|
+
@Field({ nullable: true })
|
|
21
|
+
phone?: string
|
|
22
|
+
|
|
23
|
+
@Field({ nullable: true })
|
|
24
|
+
dob?: Date
|
|
25
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './lib/api-account-feature.module'
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Module } from '@nestjs/common'
|
|
2
|
+
import { ApiAccountDataAccessModule } from '@<%= npmScope %>/api/account/data-access'
|
|
3
|
+
import { ApiAccountFeatureResolver } from './api-account-feature.resolver'
|
|
4
|
+
|
|
5
|
+
@Module({
|
|
6
|
+
imports: [ApiAccountDataAccessModule],
|
|
7
|
+
providers: [ApiAccountFeatureResolver],
|
|
8
|
+
})
|
|
9
|
+
export class ApiAccountFeatureModule {}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { UseGuards, ValidationPipe } from '@nestjs/common'
|
|
2
|
+
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql'
|
|
3
|
+
import {
|
|
4
|
+
AccountCreateEmailInput,
|
|
5
|
+
AccountUpdatePasswordInput,
|
|
6
|
+
AccountUpdateProfileInput,
|
|
7
|
+
ApiAccountDataAccessService,
|
|
8
|
+
} from '@<%= npmScope %>/api/account/data-access'
|
|
9
|
+
import { CtxUser } from '@<%= npmScope %>/api/custom'
|
|
10
|
+
import { GqlAuthGuard} from '@<%= npmScope %>/api/core/data-access'
|
|
11
|
+
import { User, Email } from '@<%= npmScope %>/api/core/models'
|
|
12
|
+
|
|
13
|
+
@Resolver()
|
|
14
|
+
@UseGuards(GqlAuthGuard)
|
|
15
|
+
export class ApiAccountFeatureResolver {
|
|
16
|
+
constructor(private readonly service: ApiAccountDataAccessService) {}
|
|
17
|
+
|
|
18
|
+
@Query(() => [Email], { nullable: true })
|
|
19
|
+
accountEmails(@CtxUser() user: User) {
|
|
20
|
+
return this.service.accountEmails(user.id)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@Query(() => User, { nullable: true })
|
|
24
|
+
accountProfile(@CtxUser() user: User) {
|
|
25
|
+
return this.service.accountProfile(user.id)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@Query(() => Boolean)
|
|
29
|
+
accountDisplayNameAvailable(
|
|
30
|
+
@CtxUser() user: User,
|
|
31
|
+
@Args('displayName') displayName: string
|
|
32
|
+
) {
|
|
33
|
+
return this.service.accountDisplayNameAvailable(user.id, displayName)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@Mutation(() => Email, { nullable: true })
|
|
37
|
+
accountCreateEmail(@CtxUser() user: User, @Args('input', new ValidationPipe()) input: AccountCreateEmailInput) {
|
|
38
|
+
return this.service.accountCreateEmail(user.id, input)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@Mutation(() => Email, { nullable: true })
|
|
42
|
+
accountDeleteEmail(@CtxUser() user: User, @Args('emailId') emailId: string) {
|
|
43
|
+
return this.service.accountDeleteEmail(user.id, emailId)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
@Mutation(() => Email, { nullable: true })
|
|
47
|
+
accountMarkEmailPrimary(@CtxUser() user: User, @Args('emailId') emailId: string) {
|
|
48
|
+
return this.service.accountMarkEmailPrimary(user.id, emailId)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@Mutation(() => Email, { nullable: true })
|
|
52
|
+
accountMarkEmailPrivate(@CtxUser() user: User, @Args('emailId') emailId: string) {
|
|
53
|
+
return this.service.accountMarkEmailPrivate(user.id, emailId)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@Mutation(() => Email, { nullable: true })
|
|
57
|
+
accountMarkEmailPublic(@CtxUser() user: User, @Args('emailId') emailId: string) {
|
|
58
|
+
return this.service.accountMarkEmailPublic(user.id, emailId)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@Mutation(() => User, { nullable: true })
|
|
62
|
+
accountUpdateProfile(@CtxUser() user: User, @Args('input', new ValidationPipe()) input: AccountUpdateProfileInput) {
|
|
63
|
+
return this.service.accountUpdateProfile(user.id, input)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
@Mutation(() => User)
|
|
67
|
+
accountUpdateDisplayName(
|
|
68
|
+
@CtxUser() user: User,
|
|
69
|
+
@Args('displayName') displayName: string
|
|
70
|
+
) {
|
|
71
|
+
return this.service.accountUpdateDisplayName(user.id, displayName)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
@Mutation(() => Boolean, { nullable: true })
|
|
75
|
+
accountResetPassword(@CtxUser() user: User) {
|
|
76
|
+
return this.service.accountResetPassword(user.id)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@Mutation(() => Boolean, { nullable: true })
|
|
80
|
+
accountUpdatePassword(@CtxUser() user: User, @Args('input', new ValidationPipe()) input: AccountUpdatePasswordInput) {
|
|
81
|
+
return this.service.accountUpdatePassword(user.id, input)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
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('account 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(
|
|
37
|
+
tree,
|
|
38
|
+
{ name: 'account', overwrite: false },
|
|
39
|
+
expect.any(String),
|
|
40
|
+
'data-access',
|
|
41
|
+
)
|
|
42
|
+
expect(apiLibraryGenerator).toHaveBeenCalledWith(
|
|
43
|
+
tree,
|
|
44
|
+
{ name: 'account', overwrite: false },
|
|
45
|
+
expect.any(String),
|
|
46
|
+
'feature',
|
|
47
|
+
true,
|
|
48
|
+
)
|
|
49
|
+
expect(formatFiles).toHaveBeenCalledWith(tree)
|
|
50
|
+
expect(installPackagesTask).toHaveBeenCalledWith(tree)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('should pass overwrite flag to apiLibraryGenerator when overwrite is true', async () => {
|
|
54
|
+
const callback = await generator(tree, { overwrite: true })
|
|
55
|
+
callback()
|
|
56
|
+
|
|
57
|
+
expect(apiLibraryGenerator).toHaveBeenCalledWith(
|
|
58
|
+
tree,
|
|
59
|
+
{ name: 'account', overwrite: true },
|
|
60
|
+
expect.any(String),
|
|
61
|
+
'data-access',
|
|
62
|
+
)
|
|
63
|
+
expect(apiLibraryGenerator).toHaveBeenCalledWith(
|
|
64
|
+
tree,
|
|
65
|
+
{ name: 'account', overwrite: true },
|
|
66
|
+
expect.any(String),
|
|
67
|
+
'feature',
|
|
68
|
+
true,
|
|
69
|
+
)
|
|
70
|
+
})
|
|
71
|
+
})
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { formatFiles, GeneratorCallback, installPackagesTask, joinPathFragments, Tree } from '@nx/devkit'
|
|
2
|
+
import { apiLibraryGenerator } from '@nestledjs/utils'
|
|
3
|
+
import { ApiAccountGeneratorSchema } from './schema'
|
|
4
|
+
|
|
5
|
+
export default async function generateLibraries(
|
|
6
|
+
tree: Tree,
|
|
7
|
+
options: ApiAccountGeneratorSchema = {},
|
|
8
|
+
): Promise<GeneratorCallback> {
|
|
9
|
+
const templateRootPath = joinPathFragments(__dirname, './files')
|
|
10
|
+
const overwrite = options.overwrite === true
|
|
11
|
+
|
|
12
|
+
await apiLibraryGenerator(tree, { name: 'account', overwrite }, templateRootPath, 'data-access')
|
|
13
|
+
await apiLibraryGenerator(tree, { name: 'account', overwrite }, templateRootPath, 'feature', true)
|
|
14
|
+
|
|
15
|
+
await formatFiles(tree)
|
|
16
|
+
|
|
17
|
+
return () => {
|
|
18
|
+
installPackagesTask(tree)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/schema",
|
|
3
|
+
"$id": "ApiAccount",
|
|
4
|
+
"title": "Create an API Account Library",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {
|
|
7
|
+
"overwrite": {
|
|
8
|
+
"type": "boolean",
|
|
9
|
+
"description": "Whether to overwrite existing libraries if they exist.",
|
|
10
|
+
"default": false
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|