├── .editorconfig
├── .env.example
├── .eslintrc.json
├── .gitignore
├── .prettierrc
├── .vscode
└── extensions.json
├── Dockerfile
├── README.md
├── apps
├── .gitkeep
├── mobile
│ ├── .build.env
│ ├── .eslintrc.json
│ ├── android
│ │ ├── .gitignore
│ │ ├── .idea
│ │ │ ├── compiler.xml
│ │ │ ├── jarRepositories.xml
│ │ │ └── misc.xml
│ │ ├── app
│ │ │ ├── .gitignore
│ │ │ ├── build.gradle
│ │ │ ├── capacitor.build.gradle
│ │ │ ├── proguard-rules.pro
│ │ │ └── src
│ │ │ │ ├── androidTest
│ │ │ │ └── java
│ │ │ │ │ └── com
│ │ │ │ │ └── getcapacitor
│ │ │ │ │ └── myapp
│ │ │ │ │ └── ExampleInstrumentedTest.java
│ │ │ │ ├── main
│ │ │ │ ├── AndroidManifest.xml
│ │ │ │ ├── assets
│ │ │ │ │ ├── capacitor.config.json
│ │ │ │ │ └── capacitor.plugins.json
│ │ │ │ ├── java
│ │ │ │ │ └── com
│ │ │ │ │ │ └── example
│ │ │ │ │ │ └── vnts
│ │ │ │ │ │ └── MainActivity.java
│ │ │ │ └── res
│ │ │ │ │ ├── drawable-land-hdpi
│ │ │ │ │ └── splash.png
│ │ │ │ │ ├── drawable-land-mdpi
│ │ │ │ │ └── splash.png
│ │ │ │ │ ├── drawable-land-xhdpi
│ │ │ │ │ └── splash.png
│ │ │ │ │ ├── drawable-land-xxhdpi
│ │ │ │ │ └── splash.png
│ │ │ │ │ ├── drawable-land-xxxhdpi
│ │ │ │ │ └── splash.png
│ │ │ │ │ ├── drawable-port-hdpi
│ │ │ │ │ └── splash.png
│ │ │ │ │ ├── drawable-port-mdpi
│ │ │ │ │ └── splash.png
│ │ │ │ │ ├── drawable-port-xhdpi
│ │ │ │ │ └── splash.png
│ │ │ │ │ ├── drawable-port-xxhdpi
│ │ │ │ │ └── splash.png
│ │ │ │ │ ├── drawable-port-xxxhdpi
│ │ │ │ │ └── splash.png
│ │ │ │ │ ├── drawable-v24
│ │ │ │ │ └── ic_launcher_foreground.xml
│ │ │ │ │ ├── drawable
│ │ │ │ │ ├── ic_launcher_background.xml
│ │ │ │ │ └── splash.png
│ │ │ │ │ ├── layout
│ │ │ │ │ └── activity_main.xml
│ │ │ │ │ ├── mipmap-anydpi-v26
│ │ │ │ │ ├── ic_launcher.xml
│ │ │ │ │ └── ic_launcher_round.xml
│ │ │ │ │ ├── mipmap-hdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ ├── ic_launcher_foreground.png
│ │ │ │ │ └── ic_launcher_round.png
│ │ │ │ │ ├── mipmap-mdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ ├── ic_launcher_foreground.png
│ │ │ │ │ └── ic_launcher_round.png
│ │ │ │ │ ├── mipmap-xhdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ ├── ic_launcher_foreground.png
│ │ │ │ │ └── ic_launcher_round.png
│ │ │ │ │ ├── mipmap-xxhdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ ├── ic_launcher_foreground.png
│ │ │ │ │ └── ic_launcher_round.png
│ │ │ │ │ ├── mipmap-xxxhdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ ├── ic_launcher_foreground.png
│ │ │ │ │ └── ic_launcher_round.png
│ │ │ │ │ ├── values
│ │ │ │ │ ├── ic_launcher_background.xml
│ │ │ │ │ ├── strings.xml
│ │ │ │ │ └── styles.xml
│ │ │ │ │ └── xml
│ │ │ │ │ ├── config.xml
│ │ │ │ │ └── file_paths.xml
│ │ │ │ └── test
│ │ │ │ └── java
│ │ │ │ └── com
│ │ │ │ └── getcapacitor
│ │ │ │ └── myapp
│ │ │ │ └── ExampleUnitTest.java
│ │ ├── build.gradle
│ │ ├── capacitor.settings.gradle
│ │ ├── gradle.properties
│ │ ├── gradle
│ │ │ └── wrapper
│ │ │ │ ├── gradle-wrapper.jar
│ │ │ │ └── gradle-wrapper.properties
│ │ ├── gradlew
│ │ ├── gradlew.bat
│ │ ├── settings.gradle
│ │ └── variables.gradle
│ ├── capacitor.config.ts
│ ├── index.html
│ ├── package.json
│ ├── project.json
│ ├── src
│ │ ├── app
│ │ │ ├── App.vue
│ │ │ ├── components
│ │ │ │ ├── AppAccountMenu.vue
│ │ │ │ ├── AppCard.vue
│ │ │ │ ├── AppDialog.vue
│ │ │ │ ├── AppDrawer.vue
│ │ │ │ ├── AppNetworkError.vue
│ │ │ │ ├── AppPage.vue
│ │ │ │ ├── AppPageHeader.vue
│ │ │ │ ├── AvatarChip.vue
│ │ │ │ ├── headers
│ │ │ │ │ └── DashboardHeader.vue
│ │ │ │ └── inputs
│ │ │ │ │ ├── VInput.vue
│ │ │ │ │ └── VSelect.vue
│ │ │ ├── layouts
│ │ │ │ ├── app.vue
│ │ │ │ └── default.vue
│ │ │ └── pages
│ │ │ │ ├── dashboard.vue
│ │ │ │ ├── login.vue
│ │ │ │ └── settings.vue
│ │ ├── assets
│ │ │ ├── main.scss
│ │ │ └── quasar.scss
│ │ ├── common
│ │ │ ├── api
│ │ │ │ ├── client.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── interceptors.ts
│ │ │ │ ├── modules
│ │ │ │ │ ├── auth.ts
│ │ │ │ │ └── users.ts
│ │ │ │ └── response-error.ts
│ │ │ ├── hooks
│ │ │ │ ├── index.ts
│ │ │ │ ├── use-logout-action.ts
│ │ │ │ └── use-promise-state.ts
│ │ │ └── index.ts
│ │ ├── config.ts
│ │ ├── locales
│ │ │ ├── en.yml
│ │ │ └── pl.yml
│ │ ├── main.ts
│ │ ├── public
│ │ │ └── favicon.ico
│ │ ├── router
│ │ │ ├── index.ts
│ │ │ ├── middlewares
│ │ │ │ ├── index.ts
│ │ │ │ ├── use-auth-guard.ts
│ │ │ │ └── use-loading-indicator.ts
│ │ │ └── routes.ts
│ │ ├── shims-vue.d.ts
│ │ └── stores
│ │ │ ├── account.ts
│ │ │ ├── app.ts
│ │ │ └── index.ts
│ ├── tsconfig.app.json
│ ├── tsconfig.json
│ ├── tsconfig.spec.json
│ └── vite.config.ts
├── server
│ ├── .eslintrc.json
│ ├── jest.config.ts
│ ├── project.json
│ ├── src
│ │ ├── app
│ │ │ ├── app.module.ts
│ │ │ ├── auth
│ │ │ │ ├── auth.controller.ts
│ │ │ │ ├── auth.module.ts
│ │ │ │ ├── auth.service.ts
│ │ │ │ ├── decorators
│ │ │ │ │ ├── auth-session.decorator.ts
│ │ │ │ │ ├── auth-user.decorator.ts
│ │ │ │ │ ├── public.decorator.ts
│ │ │ │ │ └── roles.decorator.ts
│ │ │ │ ├── guards
│ │ │ │ │ ├── jwt-auth.guard.ts
│ │ │ │ │ └── roles.guard.ts
│ │ │ │ ├── strategies
│ │ │ │ │ └── jwt.strategy.ts
│ │ │ │ └── utils
│ │ │ │ │ └── hash-password.ts
│ │ │ ├── cli
│ │ │ │ ├── cli.module.ts
│ │ │ │ ├── seed.command.ts
│ │ │ │ └── seed
│ │ │ │ │ └── seed-data.ts
│ │ │ ├── cron
│ │ │ │ ├── cron.module.ts
│ │ │ │ └── cron.service.ts
│ │ │ ├── db
│ │ │ │ ├── database.module.ts
│ │ │ │ ├── session.entity.ts
│ │ │ │ └── user.entity.ts
│ │ │ └── users
│ │ │ │ ├── users.controller.ts
│ │ │ │ ├── users.module.ts
│ │ │ │ └── users.service.ts
│ │ ├── assets
│ │ │ └── .gitkeep
│ │ ├── cli.ts
│ │ ├── common
│ │ │ ├── config.ts
│ │ │ ├── filesystem.ts
│ │ │ ├── index.ts
│ │ │ └── pipes
│ │ │ │ └── yup-validation.pipe.ts
│ │ ├── main.ts
│ │ └── static
│ │ │ └── index.html
│ ├── tsconfig.app.json
│ ├── tsconfig.json
│ ├── tsconfig.spec.json
│ └── webpack.config.js
└── web
│ ├── .build.env
│ ├── .electron.env
│ ├── .eslintrc.json
│ ├── electron
│ ├── electron-env.d.ts
│ ├── main
│ │ └── index.ts
│ └── preload
│ │ └── index.ts
│ ├── index.html
│ ├── project.json
│ ├── src
│ ├── app
│ │ ├── App.vue
│ │ ├── components
│ │ │ ├── AppAccountMenu.vue
│ │ │ ├── AppCard.vue
│ │ │ ├── AppDialog.vue
│ │ │ ├── AppDrawer.vue
│ │ │ ├── AppNetworkError.vue
│ │ │ ├── AppPage.vue
│ │ │ ├── AppPageHeader.vue
│ │ │ ├── AvatarChip.vue
│ │ │ ├── dialogs
│ │ │ │ └── UserCreateDialog.vue
│ │ │ ├── headers
│ │ │ │ ├── DashboardHeader.vue
│ │ │ │ └── UserHeader.vue
│ │ │ └── inputs
│ │ │ │ ├── VInput.vue
│ │ │ │ └── VSelect.vue
│ │ ├── layouts
│ │ │ ├── app.vue
│ │ │ └── default.vue
│ │ └── pages
│ │ │ ├── dashboard.vue
│ │ │ ├── login.vue
│ │ │ ├── settings.vue
│ │ │ └── users
│ │ │ ├── index.vue
│ │ │ ├── list.vue
│ │ │ └── user.vue
│ ├── assets
│ │ ├── main.scss
│ │ └── quasar.scss
│ ├── common
│ │ ├── api
│ │ │ ├── client.ts
│ │ │ ├── index.ts
│ │ │ ├── interceptors.ts
│ │ │ ├── modules
│ │ │ │ ├── auth.ts
│ │ │ │ └── users.ts
│ │ │ └── response-error.ts
│ │ ├── hooks
│ │ │ ├── index.ts
│ │ │ ├── use-logout-action.ts
│ │ │ └── use-promise-state.ts
│ │ └── index.ts
│ ├── config.ts
│ ├── locales
│ │ ├── en.yml
│ │ └── pl.yml
│ ├── main.ts
│ ├── public
│ │ └── favicon.ico
│ ├── router
│ │ ├── index.ts
│ │ ├── middlewares
│ │ │ ├── index.ts
│ │ │ ├── use-auth-guard.ts
│ │ │ └── use-loading-indicator.ts
│ │ └── routes.ts
│ ├── shims-vue.d.ts
│ └── stores
│ │ ├── account.ts
│ │ ├── app.ts
│ │ └── index.ts
│ ├── tsconfig.app.json
│ ├── tsconfig.json
│ ├── tsconfig.spec.json
│ └── vite.config.ts
├── docker-compose.yml
├── electron-builder.yml
├── jest.config.ts
├── jest.preset.js
├── libs
├── .gitkeep
└── shared
│ ├── .babelrc
│ ├── .eslintrc.json
│ ├── README.md
│ ├── jest.config.ts
│ ├── project.json
│ ├── src
│ ├── auth
│ │ ├── index.ts
│ │ ├── inputs
│ │ │ └── auth-login.input.ts
│ │ └── responses
│ │ │ └── auth-login.response.ts
│ ├── common
│ │ ├── index.ts
│ │ ├── inputs
│ │ │ └── pagination.input.ts
│ │ ├── responses
│ │ │ └── pagination.response.ts
│ │ └── role.enum.ts
│ ├── index.ts
│ ├── users
│ │ ├── index.ts
│ │ ├── inputs
│ │ │ ├── user-create.input.ts
│ │ │ ├── user-update-self-password.input.ts
│ │ │ ├── user-update-self.input.ts
│ │ │ └── user-update.input.ts
│ │ └── responses
│ │ │ └── user-profile.response.ts
│ └── utils
│ │ ├── compare-roles.ts
│ │ ├── index.ts
│ │ ├── timeout.ts
│ │ ├── use-schema.ts
│ │ └── yup-locale.ts
│ ├── tsconfig.json
│ ├── tsconfig.lib.json
│ └── tsconfig.spec.json
├── nx.json
├── package-lock.json
├── package.json
├── tools
├── generators
│ └── .gitkeep
├── scripts
│ └── patch-graph.js
└── tsconfig.tools.json
└── tsconfig.base.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see http://editorconfig.org
2 | root = true
3 |
4 | [*.{ts,js,vue}]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 4
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 | end_of_line = lf
11 |
12 | [*.md]
13 | max_line_length = off
14 | trim_trailing_whitespace = false
15 | end_of_line = lf
16 |
17 | [*.json]
18 | charset = utf-8
19 | indent_style = space
20 | indent_size = 2
21 | end_of_line = lf
22 |
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | # Front-end: API server connection configuration
2 | VITE_WEB_DEFAULT_LOCALE="en"
3 | VITE_WEB_API_URL="http://localhost"
4 | VITE_WEB_API_PORT=3000
5 |
6 | # HTTP / HTTPS server config
7 | NEST_API_HTTP_PORT=3000
8 | NEST_API_HTTP_SSL=false
9 | NEST_API_HTTP_KEY=""
10 | NEST_API_HTTP_CERT=""
11 |
12 | # Cross-Origin Resource Sharing domain origins
13 | # More info: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
14 | NEST_API_HTTP_CORS=["http://localhost", "http://localhost:8080", "http://localhost:8090", "app://localhost", "capacitor://localhost"]
15 |
16 | # Keys required for hashing passwords and tokens
17 | # They should be filled with random, unique strings
18 | NEST_API_SECRETS_PWDSALT=""
19 | NEST_API_SECRETS_JWT=""
20 |
21 | # Database type: postgres, mysql, sqlite etc.
22 | # More info: https://typeorm.io
23 | DATABASE_TYPE="postgres"
24 |
25 | # Database connection config
26 | DATABASE_HOST="localhost"
27 | DATABASE_PORT=5432
28 |
29 | # Database name and user credentials
30 | DATABASE_NAME=""
31 | DATABASE_USER=""
32 | DATABASE_PASSWORD=""
33 |
34 | # Disable this in the production version of the application
35 | # More info: https://typeorm.io/faq#how-do-i-update-a-database-schema
36 | DATABASE_ENABLE_SYNC=true
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 |
4 | "ignorePatterns": ["node_modules", "dist", "build", "components.d.ts"],
5 |
6 | "overrides": [
7 | {
8 | "files": ["*.ts", ".tsx"],
9 |
10 | "extends": [
11 | "eslint:recommended",
12 | "plugin:@typescript-eslint/recommended",
13 | "plugin:prettier/recommended"
14 | ],
15 |
16 | "env": {
17 | "es6": true,
18 | "browser": true,
19 | "node": true
20 | },
21 |
22 | "plugins": [
23 | "@typescript-eslint",
24 | "prettier"
25 | ],
26 |
27 | "rules": {
28 | "prettier/prettier": "warn",
29 |
30 | "@typescript-eslint/no-floating-promises": "off",
31 | "@typescript-eslint/require-await": "off",
32 | "@typescript-eslint/no-explicit-any": "off",
33 | "@typescript-eslint/no-unsafe-call": "off",
34 | "@typescript-eslint/no-unsafe-member-access": "off",
35 | "@typescript-eslint/no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }],
36 |
37 | "semi": ["error", "always"],
38 | "quotes": ["warn", "single", { "allowTemplateLiterals": true }]
39 | }
40 | },
41 |
42 | {
43 | "files": ["*.vue"],
44 |
45 | "extends": [
46 | "plugin:vue/vue3-essential",
47 | "@vue/eslint-config-typescript/recommended",
48 | "@vue/eslint-config-prettier"
49 | ],
50 |
51 | "plugins": [
52 | "prettier"
53 | ],
54 |
55 | "env": {
56 | "vue/setup-compiler-macros": true
57 | },
58 |
59 | "rules": {
60 | "prettier/prettier": "warn",
61 |
62 | "@typescript-eslint/no-floating-promises": "off",
63 | "@typescript-eslint/require-await": "off",
64 | "@typescript-eslint/no-explicit-any": "off",
65 | "@typescript-eslint/no-unsafe-call": "off",
66 | "@typescript-eslint/no-unsafe-member-access": "off",
67 | "@typescript-eslint/no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }],
68 |
69 | //nuxt standard
70 | "vue/multi-word-component-names": "off",
71 |
72 | "semi": ["error", "always"],
73 | "quotes": ["warn", "single", { "allowTemplateLiterals": true }]
74 | }
75 | },
76 |
77 | {
78 | "files": ["*.js"],
79 |
80 | "plugins": [
81 | "prettier"
82 | ],
83 |
84 | "parserOptions": {
85 | "ecmaVersion": 2018
86 | },
87 |
88 | "env": {
89 | "es6": true
90 | },
91 |
92 | "rules": {
93 | "prettier/prettier": "warn",
94 |
95 | "semi": ["error", "always"],
96 | "quotes": ["warn", "single", { "allowTemplateLiterals": true }]
97 | }
98 | }
99 | ]
100 | }
101 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist
5 | /tmp
6 | /out-tsc
7 |
8 | # dependencies
9 | node_modules
10 |
11 | # IDEs and editors
12 | /.idea
13 | .project
14 | .classpath
15 | .c9/
16 | *.launch
17 | .settings/
18 | *.sublime-workspace
19 |
20 | # IDE - VSCode
21 | .vscode/*
22 | !.vscode/settings.json
23 | !.vscode/tasks.json
24 | !.vscode/launch.json
25 | !.vscode/extensions.json
26 |
27 | # misc
28 | /.sass-cache
29 | /connect.lock
30 | /coverage
31 | /libpeerconnection.log
32 | npm-debug.log
33 | yarn-error.log
34 | testem.log
35 | /typings
36 |
37 | # System Files
38 | .DS_Store
39 | Thumbs.db
40 |
41 | .env
42 | apps/**/components.d.ts
43 | apps/**/auto-imports.d.ts
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "tabWidth": 4,
3 | "singleQuote": true,
4 | "endOfLine": "auto",
5 | "printWidth": 100
6 | }
7 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "nrwl.angular-console",
4 | "vue.volar",
5 | "dbaeumer.vscode-eslint",
6 | "editorconfig.editorconfig",
7 | "syler.sass-indented",
8 | "eamodio.gitlens",
9 | "aaron-bond.better-comments",
10 | "visualstudioexptteam.vscodeintellicode",
11 | "pkief.material-icon-theme",
12 | "mikestead.dotenv",
13 | "firsttris.vscode-jest-runner"
14 | ],
15 | "unwantedRecommendations": ["octref.vetur"]
16 | }
17 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:18.13-alpine
2 | WORKDIR /usr
3 |
4 | COPY package.json ./
5 | COPY package-lock.json ./
6 | COPY dist/apps/server ./server
7 |
8 | RUN ls -a
9 | RUN npm install
10 |
11 | CMD node ./server/main.js
--------------------------------------------------------------------------------
/apps/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DhivinX/nx-nestjs-vue/9efaa801ec6b936aeeff49b7a120ad9b92bae51a/apps/.gitkeep
--------------------------------------------------------------------------------
/apps/mobile/.build.env:
--------------------------------------------------------------------------------
1 | VITE_DISABLE_VUE_TSC=true
--------------------------------------------------------------------------------
/apps/mobile/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "../../.eslintrc.json"
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/apps/mobile/android/.gitignore:
--------------------------------------------------------------------------------
1 | # Using Android gitignore template: https://github.com/github/gitignore/blob/HEAD/Android.gitignore
2 |
3 | # Built application files
4 | *.apk
5 | *.aar
6 | *.ap_
7 | *.aab
8 |
9 | # Files for the ART/Dalvik VM
10 | *.dex
11 |
12 | # Java class files
13 | *.class
14 |
15 | # Generated files
16 | bin/
17 | gen/
18 | out/
19 | # Uncomment the following line in case you need and you don't have the release build type files in your app
20 | # release/
21 |
22 | # Gradle files
23 | .gradle/
24 | build/
25 |
26 | # Local configuration file (sdk path, etc)
27 | local.properties
28 |
29 | # Proguard folder generated by Eclipse
30 | proguard/
31 |
32 | # Log Files
33 | *.log
34 |
35 | # Android Studio Navigation editor temp files
36 | .navigation/
37 |
38 | # Android Studio captures folder
39 | captures/
40 |
41 | # IntelliJ
42 | *.iml
43 | .idea/workspace.xml
44 | .idea/tasks.xml
45 | .idea/gradle.xml
46 | .idea/assetWizardSettings.xml
47 | .idea/dictionaries
48 | .idea/libraries
49 | # Android Studio 3 in .gitignore file.
50 | .idea/caches
51 | .idea/modules.xml
52 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you
53 | .idea/navEditor.xml
54 |
55 | # Keystore files
56 | # Uncomment the following lines if you do not want to check your keystore files in.
57 | #*.jks
58 | #*.keystore
59 |
60 | # External native build folder generated in Android Studio 2.2 and later
61 | .externalNativeBuild
62 | .cxx/
63 |
64 | # Google Services (e.g. APIs or Firebase)
65 | # google-services.json
66 |
67 | # Freeline
68 | freeline.py
69 | freeline/
70 | freeline_project_description.json
71 |
72 | # fastlane
73 | fastlane/report.xml
74 | fastlane/Preview.html
75 | fastlane/screenshots
76 | fastlane/test_output
77 | fastlane/readme.md
78 |
79 | # Version control
80 | vcs.xml
81 |
82 | # lint
83 | lint/intermediates/
84 | lint/generated/
85 | lint/outputs/
86 | lint/tmp/
87 | # lint/reports/
88 |
89 | # Android Profiling
90 | *.hprof
91 |
92 | # Cordova plugins for Capacitor
93 | capacitor-cordova-android-plugins
94 |
95 | # Copied web assets
96 | app/src/main/assets/public
97 |
98 | # Generated Config files
99 | app/src/main/assets/capacitor.config.json
100 | app/src/main/assets/capacitor.plugins.json
101 | app/src/main/res/xml/config.xml
102 |
--------------------------------------------------------------------------------
/apps/mobile/android/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/apps/mobile/android/.idea/jarRepositories.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/apps/mobile/android/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/apps/mobile/android/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build/*
2 | !/build/.npmkeep
3 |
--------------------------------------------------------------------------------
/apps/mobile/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | namespace "com.example.vnts"
5 | compileSdkVersion rootProject.ext.compileSdkVersion
6 | defaultConfig {
7 | applicationId "com.example.vnts"
8 | minSdkVersion rootProject.ext.minSdkVersion
9 | targetSdkVersion rootProject.ext.targetSdkVersion
10 | versionCode 1
11 | versionName "1.0"
12 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
13 | aaptOptions {
14 | // Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
15 | // Default: https://android.googlesource.com/platform/frameworks/base/+/282e181b58cf72b6ca770dc7ca5f91f135444502/tools/aapt/AaptAssets.cpp#61
16 | ignoreAssetsPattern '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~'
17 | }
18 | }
19 | buildTypes {
20 | release {
21 | minifyEnabled false
22 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
23 | }
24 | }
25 | }
26 |
27 | repositories {
28 | flatDir{
29 | dirs '../capacitor-cordova-android-plugins/src/main/libs', 'libs'
30 | }
31 | }
32 |
33 | dependencies {
34 | implementation fileTree(include: ['*.jar'], dir: 'libs')
35 | implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
36 | implementation "androidx.coordinatorlayout:coordinatorlayout:$androidxCoordinatorLayoutVersion"
37 | implementation "androidx.core:core-splashscreen:$coreSplashScreenVersion"
38 | implementation project(':capacitor-android')
39 | testImplementation "junit:junit:$junitVersion"
40 | androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
41 | androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
42 | implementation project(':capacitor-cordova-android-plugins')
43 | }
44 |
45 | apply from: 'capacitor.build.gradle'
46 |
47 | try {
48 | def servicesJSON = file('google-services.json')
49 | if (servicesJSON.text) {
50 | apply plugin: 'com.google.gms.google-services'
51 | }
52 | } catch(Exception e) {
53 | logger.info("google-services.json not found, google-services plugin not applied. Push Notifications won't work")
54 | }
55 |
--------------------------------------------------------------------------------
/apps/mobile/android/app/capacitor.build.gradle:
--------------------------------------------------------------------------------
1 | // DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
2 |
3 | android {
4 | compileOptions {
5 | sourceCompatibility JavaVersion.VERSION_17
6 | targetCompatibility JavaVersion.VERSION_17
7 | }
8 | }
9 |
10 | apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
11 | dependencies {
12 |
13 |
14 | }
15 |
16 |
17 | if (hasProperty('postBuildExtras')) {
18 | postBuildExtras()
19 | }
20 |
--------------------------------------------------------------------------------
/apps/mobile/android/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/apps/mobile/android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.getcapacitor.myapp;
2 |
3 | import static org.junit.Assert.*;
4 |
5 | import android.content.Context;
6 | import androidx.test.ext.junit.runners.AndroidJUnit4;
7 | import androidx.test.platform.app.InstrumentationRegistry;
8 | import org.junit.Test;
9 | import org.junit.runner.RunWith;
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * @see Testing documentation
15 | */
16 | @RunWith(AndroidJUnit4.class)
17 | public class ExampleInstrumentedTest {
18 |
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
23 |
24 | assertEquals("com.getcapacitor.app", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/apps/mobile/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
32 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/apps/mobile/android/app/src/main/assets/capacitor.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "appId": "com.example.vnts",
3 | "appName": "nx-nestjs-vue",
4 | "webDir": "../../dist/apps/mobile"
5 | }
6 |
--------------------------------------------------------------------------------
/apps/mobile/android/app/src/main/assets/capacitor.plugins.json:
--------------------------------------------------------------------------------
1 | []
2 |
--------------------------------------------------------------------------------
/apps/mobile/android/app/src/main/java/com/example/vnts/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.example.vnts;
2 |
3 | import com.getcapacitor.BridgeActivity;
4 |
5 | public class MainActivity extends BridgeActivity {}
6 |
--------------------------------------------------------------------------------
/apps/mobile/android/app/src/main/res/drawable-land-hdpi/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DhivinX/nx-nestjs-vue/9efaa801ec6b936aeeff49b7a120ad9b92bae51a/apps/mobile/android/app/src/main/res/drawable-land-hdpi/splash.png
--------------------------------------------------------------------------------
/apps/mobile/android/app/src/main/res/drawable-land-mdpi/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DhivinX/nx-nestjs-vue/9efaa801ec6b936aeeff49b7a120ad9b92bae51a/apps/mobile/android/app/src/main/res/drawable-land-mdpi/splash.png
--------------------------------------------------------------------------------
/apps/mobile/android/app/src/main/res/drawable-land-xhdpi/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DhivinX/nx-nestjs-vue/9efaa801ec6b936aeeff49b7a120ad9b92bae51a/apps/mobile/android/app/src/main/res/drawable-land-xhdpi/splash.png
--------------------------------------------------------------------------------
/apps/mobile/android/app/src/main/res/drawable-land-xxhdpi/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DhivinX/nx-nestjs-vue/9efaa801ec6b936aeeff49b7a120ad9b92bae51a/apps/mobile/android/app/src/main/res/drawable-land-xxhdpi/splash.png
--------------------------------------------------------------------------------
/apps/mobile/android/app/src/main/res/drawable-land-xxxhdpi/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DhivinX/nx-nestjs-vue/9efaa801ec6b936aeeff49b7a120ad9b92bae51a/apps/mobile/android/app/src/main/res/drawable-land-xxxhdpi/splash.png
--------------------------------------------------------------------------------
/apps/mobile/android/app/src/main/res/drawable-port-hdpi/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DhivinX/nx-nestjs-vue/9efaa801ec6b936aeeff49b7a120ad9b92bae51a/apps/mobile/android/app/src/main/res/drawable-port-hdpi/splash.png
--------------------------------------------------------------------------------
/apps/mobile/android/app/src/main/res/drawable-port-mdpi/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DhivinX/nx-nestjs-vue/9efaa801ec6b936aeeff49b7a120ad9b92bae51a/apps/mobile/android/app/src/main/res/drawable-port-mdpi/splash.png
--------------------------------------------------------------------------------
/apps/mobile/android/app/src/main/res/drawable-port-xhdpi/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DhivinX/nx-nestjs-vue/9efaa801ec6b936aeeff49b7a120ad9b92bae51a/apps/mobile/android/app/src/main/res/drawable-port-xhdpi/splash.png
--------------------------------------------------------------------------------
/apps/mobile/android/app/src/main/res/drawable-port-xxhdpi/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DhivinX/nx-nestjs-vue/9efaa801ec6b936aeeff49b7a120ad9b92bae51a/apps/mobile/android/app/src/main/res/drawable-port-xxhdpi/splash.png
--------------------------------------------------------------------------------
/apps/mobile/android/app/src/main/res/drawable-port-xxxhdpi/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DhivinX/nx-nestjs-vue/9efaa801ec6b936aeeff49b7a120ad9b92bae51a/apps/mobile/android/app/src/main/res/drawable-port-xxxhdpi/splash.png
--------------------------------------------------------------------------------
/apps/mobile/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/apps/mobile/android/app/src/main/res/drawable/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DhivinX/nx-nestjs-vue/9efaa801ec6b936aeeff49b7a120ad9b92bae51a/apps/mobile/android/app/src/main/res/drawable/splash.png
--------------------------------------------------------------------------------
/apps/mobile/android/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
12 |
13 |
--------------------------------------------------------------------------------
/apps/mobile/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/apps/mobile/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DhivinX/nx-nestjs-vue/9efaa801ec6b936aeeff49b7a120ad9b92bae51a/apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DhivinX/nx-nestjs-vue/9efaa801ec6b936aeeff49b7a120ad9b92bae51a/apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DhivinX/nx-nestjs-vue/9efaa801ec6b936aeeff49b7a120ad9b92bae51a/apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DhivinX/nx-nestjs-vue/9efaa801ec6b936aeeff49b7a120ad9b92bae51a/apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DhivinX/nx-nestjs-vue/9efaa801ec6b936aeeff49b7a120ad9b92bae51a/apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DhivinX/nx-nestjs-vue/9efaa801ec6b936aeeff49b7a120ad9b92bae51a/apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DhivinX/nx-nestjs-vue/9efaa801ec6b936aeeff49b7a120ad9b92bae51a/apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DhivinX/nx-nestjs-vue/9efaa801ec6b936aeeff49b7a120ad9b92bae51a/apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DhivinX/nx-nestjs-vue/9efaa801ec6b936aeeff49b7a120ad9b92bae51a/apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DhivinX/nx-nestjs-vue/9efaa801ec6b936aeeff49b7a120ad9b92bae51a/apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DhivinX/nx-nestjs-vue/9efaa801ec6b936aeeff49b7a120ad9b92bae51a/apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DhivinX/nx-nestjs-vue/9efaa801ec6b936aeeff49b7a120ad9b92bae51a/apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DhivinX/nx-nestjs-vue/9efaa801ec6b936aeeff49b7a120ad9b92bae51a/apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DhivinX/nx-nestjs-vue/9efaa801ec6b936aeeff49b7a120ad9b92bae51a/apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DhivinX/nx-nestjs-vue/9efaa801ec6b936aeeff49b7a120ad9b92bae51a/apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/apps/mobile/android/app/src/main/res/values/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFFFFF
4 |
--------------------------------------------------------------------------------
/apps/mobile/android/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | nx-nestjs-vue
4 | nx-nestjs-vue
5 | com.example.vnts
6 | com.example.vnts
7 |
8 |
--------------------------------------------------------------------------------
/apps/mobile/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
11 |
12 |
17 |
18 |
19 |
22 |
--------------------------------------------------------------------------------
/apps/mobile/android/app/src/main/res/xml/config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/apps/mobile/android/app/src/main/res/xml/file_paths.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/apps/mobile/android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.getcapacitor.myapp;
2 |
3 | import static org.junit.Assert.*;
4 |
5 | import org.junit.Test;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 |
14 | @Test
15 | public void addition_isCorrect() throws Exception {
16 | assertEquals(4, 2 + 2);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/apps/mobile/android/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 |
5 | repositories {
6 | google()
7 | mavenCentral()
8 | }
9 | dependencies {
10 | classpath 'com.android.tools.build:gradle:7.4.0'
11 | classpath 'com.google.gms:google-services:4.3.15'
12 |
13 | // NOTE: Do not place your application dependencies here; they belong
14 | // in the individual module build.gradle files
15 | }
16 | }
17 |
18 | apply from: "variables.gradle"
19 |
20 | allprojects {
21 | repositories {
22 | google()
23 | mavenCentral()
24 | }
25 | }
26 |
27 | task clean(type: Delete) {
28 | delete rootProject.buildDir
29 | }
30 |
--------------------------------------------------------------------------------
/apps/mobile/android/capacitor.settings.gradle:
--------------------------------------------------------------------------------
1 | // DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
2 | include ':capacitor-android'
3 | project(':capacitor-android').projectDir = new File('../../../node_modules/@capacitor/android/capacitor')
4 |
--------------------------------------------------------------------------------
/apps/mobile/android/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
19 | # AndroidX package structure to make it clearer which packages are bundled with the
20 | # Android operating system, and which are packaged with your app's APK
21 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
22 | android.useAndroidX=true
23 |
--------------------------------------------------------------------------------
/apps/mobile/android/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DhivinX/nx-nestjs-vue/9efaa801ec6b936aeeff49b7a120ad9b92bae51a/apps/mobile/android/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/apps/mobile/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-all.zip
4 | networkTimeout=10000
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/apps/mobile/android/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%"=="" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%"=="" set DIRNAME=.
29 | @rem This is normally unused
30 | set APP_BASE_NAME=%~n0
31 | set APP_HOME=%DIRNAME%
32 |
33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
35 |
36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
38 |
39 | @rem Find java.exe
40 | if defined JAVA_HOME goto findJavaFromJavaHome
41 |
42 | set JAVA_EXE=java.exe
43 | %JAVA_EXE% -version >NUL 2>&1
44 | if %ERRORLEVEL% equ 0 goto execute
45 |
46 | echo.
47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
48 | echo.
49 | echo Please set the JAVA_HOME variable in your environment to match the
50 | echo location of your Java installation.
51 |
52 | goto fail
53 |
54 | :findJavaFromJavaHome
55 | set JAVA_HOME=%JAVA_HOME:"=%
56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
57 |
58 | if exist "%JAVA_EXE%" goto execute
59 |
60 | echo.
61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
62 | echo.
63 | echo Please set the JAVA_HOME variable in your environment to match the
64 | echo location of your Java installation.
65 |
66 | goto fail
67 |
68 | :execute
69 | @rem Setup the command line
70 |
71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
72 |
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if %ERRORLEVEL% equ 0 goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | set EXIT_CODE=%ERRORLEVEL%
85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
87 | exit /b %EXIT_CODE%
88 |
89 | :mainEnd
90 | if "%OS%"=="Windows_NT" endlocal
91 |
92 | :omega
93 |
--------------------------------------------------------------------------------
/apps/mobile/android/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 | include ':capacitor-cordova-android-plugins'
3 | project(':capacitor-cordova-android-plugins').projectDir = new File('./capacitor-cordova-android-plugins/')
4 |
5 | apply from: 'capacitor.settings.gradle'
--------------------------------------------------------------------------------
/apps/mobile/android/variables.gradle:
--------------------------------------------------------------------------------
1 | ext {
2 | minSdkVersion = 22
3 | compileSdkVersion = 33
4 | targetSdkVersion = 33
5 | androidxActivityVersion = '1.7.0'
6 | androidxAppCompatVersion = '1.6.1'
7 | androidxCoordinatorLayoutVersion = '1.2.0'
8 | androidxCoreVersion = '1.10.0'
9 | androidxFragmentVersion = '1.5.6'
10 | coreSplashScreenVersion = '1.0.0'
11 | androidxWebkitVersion = '1.6.1'
12 | junitVersion = '4.13.2'
13 | androidxJunitVersion = '1.1.5'
14 | androidxEspressoCoreVersion = '3.5.1'
15 | cordovaAndroidVersion = '10.1.1'
16 | }
--------------------------------------------------------------------------------
/apps/mobile/capacitor.config.ts:
--------------------------------------------------------------------------------
1 | import { CapacitorConfig } from '@capacitor/cli';
2 |
3 | const config: CapacitorConfig = {
4 | appId: 'com.example.vnts',
5 | appName: 'nx-nestjs-vue',
6 | webDir: '../../dist/apps/mobile',
7 | };
8 |
9 | export default config;
10 |
--------------------------------------------------------------------------------
/apps/mobile/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | nx-nestjs-vue-mobile
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/apps/mobile/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mobile"
3 | }
4 |
--------------------------------------------------------------------------------
/apps/mobile/src/app/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
49 |
--------------------------------------------------------------------------------
/apps/mobile/src/app/components/AppAccountMenu.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
{{ accountStore.state.fullname }}
6 |
7 | {{
8 | accountStore.state.position ? `${accountStore.state.position} • ` : ''
9 | }}
10 | {{ accountStore.state.roleName }}
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | {{ $t('account_menu_settings') }}
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | {{ $t('account_menu_logout') }}
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
61 |
62 |
71 |
--------------------------------------------------------------------------------
/apps/mobile/src/app/components/AppCard.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
18 |
--------------------------------------------------------------------------------
/apps/mobile/src/app/components/AppDialog.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
{{ props.title }}
7 |
8 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
46 |
--------------------------------------------------------------------------------
/apps/mobile/src/app/components/AppNetworkError.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 | {{ $t('app_connection_lost') }}
10 |
11 |
12 |
13 |
17 |
--------------------------------------------------------------------------------
/apps/mobile/src/app/components/AppPage.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
29 |
--------------------------------------------------------------------------------
/apps/mobile/src/app/components/AppPageHeader.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | {{ props.title }}
8 |
9 |
10 |
11 |
12 |
13 |
14 |
22 |
--------------------------------------------------------------------------------
/apps/mobile/src/app/components/AvatarChip.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | {{ props.title }}
9 |
10 |
11 | {{ props.subtitle }}
12 |
13 |
14 |
15 |
16 |
17 |
18 | {{ props.title }}
19 |
20 |
21 | {{ props.subtitle }}
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
41 |
42 |
52 |
--------------------------------------------------------------------------------
/apps/mobile/src/app/components/headers/DashboardHeader.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ $t('dashboard_welcome', { name: accountStore.state.firstName }) }}
6 |
7 |
{{ accountStore.state.email }}
8 |
9 |
10 |
11 |
12 |
16 |
--------------------------------------------------------------------------------
/apps/mobile/src/app/components/inputs/VInput.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
46 |
--------------------------------------------------------------------------------
/apps/mobile/src/app/components/inputs/VSelect.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
49 |
--------------------------------------------------------------------------------
/apps/mobile/src/app/layouts/app.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
14 |
15 |
16 |
17 |
18 |
23 |
24 |
25 |
26 |
27 |
28 |
38 |
--------------------------------------------------------------------------------
/apps/mobile/src/app/layouts/default.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/apps/mobile/src/app/pages/dashboard.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/apps/mobile/src/assets/main.scss:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700;800;900&display=swap');
2 |
3 | body:not(i) {
4 | font-family: 'Poppins', sans-serif;
5 | }
6 |
7 | .app-header {
8 | box-shadow: 0 12px 12px -12px rgb(0 0 0 / 20%);
9 | }
10 |
11 | .app-drawer {
12 | background-color: #111827;
13 | color: #ffffff;
14 | box-shadow: 0px 0px 12px rgb(0 0 0 / 20%);
15 | }
16 |
17 | .app-page-container {
18 | background-color: #f1f5f9;
19 | min-height: 100px;
20 | }
21 |
22 | .container {
23 | margin: auto;
24 | padding: 38px;
25 | max-width: 1600px;
26 |
27 | @media (max-width: 600px){
28 | padding: 18px;
29 | }
30 | }
31 |
32 | .border-bottom-accent {
33 | border-bottom: 1px solid #e2e8f0;
34 | }
35 |
36 | .q-card {
37 | border-radius: 20px;
38 | box-shadow: 0px 2px 3px -3px rgba(66, 68, 90, 0.72);
39 | display: flex;
40 | flex-direction: column;
41 |
42 | .card-title {
43 | font-weight: 500;
44 | font-size: 24px;
45 | margin-bottom: 5px;
46 | }
47 |
48 | >form{
49 | height: 100%;
50 | display: flex;
51 | flex-direction: column;
52 | }
53 |
54 | .q-card__section {
55 | padding: 10px 30px;
56 |
57 | &:first-child {
58 | padding-top: 30px;
59 | }
60 |
61 | &:last-child {
62 | padding-bottom: 30px;
63 | }
64 | }
65 |
66 | .q-card__actions {
67 | padding: 30px;
68 | display: flex;
69 | align-items: flex-end;
70 | flex-grow: 1;
71 |
72 | .q-btn {
73 | font-size: 14px;
74 | padding: 8px 15px 8px 15px;
75 | }
76 | }
77 | }
78 |
79 | .q-pagination {
80 | .q-btn {
81 | font-size: 14px;
82 | line-height: 13px;
83 | padding: 8px !important;
84 | }
85 | }
86 |
87 | .q-dialog-max-width {
88 | width: 100%;
89 | max-width: 500px;
90 | }
91 |
--------------------------------------------------------------------------------
/apps/mobile/src/assets/quasar.scss:
--------------------------------------------------------------------------------
1 | $primary : #4F46E5;
2 | $secondary : #111827;
3 | $accent : #9C27B0;
4 |
5 | $dark : #1D1D1D;
6 |
7 | $positive : #21BA45;
8 | $negative : #EF5350;
9 | $info : #31CCEC;
10 | $warning : #F2C037;
11 |
--------------------------------------------------------------------------------
/apps/mobile/src/common/api/client.ts:
--------------------------------------------------------------------------------
1 | import { config } from '@/config';
2 | import axios from 'axios';
3 |
4 | export const $axios = axios.create({
5 | baseURL: `${config.api.host}:${config.api.port}/api`,
6 | withCredentials: true,
7 | });
8 |
9 | export function setAuthorizationToken(token: string): void {
10 | $axios.defaults.headers.common['Authorization'] = `Bearer ${token}`;
11 | }
12 |
13 | export function clearAuthorizationToken(): void {
14 | $axios.defaults.headers.common['Authorization'] = undefined;
15 | }
16 |
--------------------------------------------------------------------------------
/apps/mobile/src/common/api/index.ts:
--------------------------------------------------------------------------------
1 | export * from './client';
2 | export * from './interceptors';
3 | export * from './response-error';
4 |
5 | import * as auth from './modules/auth';
6 | import * as users from './modules/users';
7 |
8 | export const api = {
9 | auth,
10 | users,
11 | };
12 |
--------------------------------------------------------------------------------
/apps/mobile/src/common/api/interceptors.ts:
--------------------------------------------------------------------------------
1 | import { AxiosResponse } from 'axios';
2 | import { timeout } from '@workspace/shared';
3 | import { $axios } from './client';
4 | import { ResponseError } from './response-error';
5 |
6 | type ResponseSuccessCallback = (response: AxiosResponse) => void;
7 | type ResponseErrorCallback = (error: ResponseError) => void;
8 |
9 | interface CallbackTrigger {
10 | responseSuccess: ResponseSuccessCallback;
11 | responseError: ResponseErrorCallback;
12 | }
13 |
14 | const callbackTrigger: CallbackTrigger = {
15 | responseSuccess: null,
16 | responseError: null,
17 | };
18 |
19 | $axios.interceptors.response.use(
20 | (response: AxiosResponse) => {
21 | if (callbackTrigger.responseSuccess) callbackTrigger.responseSuccess(response);
22 | return response;
23 | },
24 |
25 | async (error: ResponseError) => {
26 | if (error.response && error.response.status !== 0) {
27 | error.isNetworkError = false;
28 | if (callbackTrigger.responseError) callbackTrigger.responseError(error);
29 | return Promise.reject(error);
30 | } else {
31 | error.isNetworkError = true;
32 | if (callbackTrigger.responseError) callbackTrigger.responseError(error);
33 | await timeout(5000);
34 | return await $axios.request(error.config);
35 | }
36 | }
37 | );
38 |
39 | export function onResponseSuccess(callback: ResponseSuccessCallback): void {
40 | callbackTrigger.responseSuccess = callback;
41 | }
42 |
43 | export function onResponseError(callback: ResponseErrorCallback): void {
44 | callbackTrigger.responseError = callback;
45 | }
46 |
--------------------------------------------------------------------------------
/apps/mobile/src/common/api/modules/auth.ts:
--------------------------------------------------------------------------------
1 | import { AuthLoginDto, AuthLoginResponse } from '@workspace/shared';
2 | import { $axios } from '../client';
3 |
4 | export async function login(authLoginDto: AuthLoginDto) {
5 | return await $axios.post('/auth/login', authLoginDto);
6 | }
7 |
8 | export async function logout() {
9 | return await $axios.post('/auth/logout');
10 | }
11 |
--------------------------------------------------------------------------------
/apps/mobile/src/common/api/modules/users.ts:
--------------------------------------------------------------------------------
1 | import {
2 | PaginationDto,
3 | PaginationResponse,
4 | UserCreateDto,
5 | UserProfileResponse,
6 | UserUpdateDto,
7 | UserUpdateSelfDto,
8 | UserUpdateSelfPasswordDto,
9 | } from '@workspace/shared';
10 |
11 | import { $axios } from '../client';
12 |
13 | export async function getSelf() {
14 | return await $axios.get('/users/self');
15 | }
16 |
17 | export async function updateSelf(data: UserUpdateSelfDto) {
18 | return await $axios.put('/users/self', data);
19 | }
20 |
21 | export async function updateSelfPassword(data: UserUpdateSelfPasswordDto) {
22 | return await $axios.put('/users/self/password', data);
23 | }
24 |
25 | export async function createOne(data: UserCreateDto) {
26 | return await $axios.post('/users/', data);
27 | }
28 |
29 | export async function getMany(data: PaginationDto) {
30 | return await $axios.get>(`/users/`, {
31 | params: data,
32 | });
33 | }
34 |
35 | export async function getOne(id: string) {
36 | return await $axios.get(`/users/${id}`);
37 | }
38 |
39 | export async function updateOne(id: string, data: UserUpdateDto) {
40 | return await $axios.put(`/users/${id}`, data);
41 | }
42 |
43 | export async function deleteOne(id: string) {
44 | return await $axios.delete(`/users/${id}`);
45 | }
46 |
--------------------------------------------------------------------------------
/apps/mobile/src/common/api/response-error.ts:
--------------------------------------------------------------------------------
1 | import { AxiosError } from 'axios';
2 |
3 | export interface ResponseError extends AxiosError {
4 | isNetworkError: boolean;
5 | }
6 |
--------------------------------------------------------------------------------
/apps/mobile/src/common/hooks/index.ts:
--------------------------------------------------------------------------------
1 | export * from './use-promise-state';
2 | export * from './use-logout-action';
3 |
--------------------------------------------------------------------------------
/apps/mobile/src/common/hooks/use-logout-action.ts:
--------------------------------------------------------------------------------
1 | import { useAccountStore } from '@/stores/account';
2 | import { useQuasar, DialogChainObject } from 'quasar';
3 | import { reactive } from 'vue';
4 | import { useI18n } from 'vue-i18n';
5 | import { useRouter } from 'vue-router';
6 | import { api } from '../api';
7 | import { usePromiseState, UsePromiseStateRetrun } from './use-promise-state';
8 |
9 | export interface UseLogoutActionReturn
10 | extends UsePromiseStateRetrun {
11 | logout: () => void;
12 | }
13 |
14 | export function useLogoutAction(): UseLogoutActionReturn {
15 | const router = useRouter();
16 | const $q = useQuasar();
17 | const { t } = useI18n();
18 |
19 | const accountStore = useAccountStore();
20 |
21 | let dialog: DialogChainObject | undefined = undefined;
22 |
23 | function showLogoutDialog(): void {
24 | dialog = $q.dialog({
25 | message: t('account_logout_progress'),
26 | progress: true,
27 | persistent: true,
28 | ok: false,
29 | color: 'primary',
30 | });
31 | }
32 |
33 | function hideLogoutDialog(): void {
34 | if (dialog) dialog.hide();
35 | }
36 |
37 | const logoutAction = usePromiseState(
38 | async () => {
39 | await api.auth.logout();
40 | accountStore.setAuthenticated(false);
41 | hideLogoutDialog();
42 |
43 | $q.notify({
44 | icon: 'mdi-check',
45 | color: 'positive',
46 | message: t('account_logout_success'),
47 | timeout: 1000,
48 | });
49 |
50 | router.push({ name: 'login' });
51 | },
52 | () => {
53 | hideLogoutDialog();
54 |
55 | $q.notify({
56 | icon: 'mdi-close',
57 | color: 'negative',
58 | message: t('account_logout_failed'),
59 | timeout: 1000,
60 | });
61 | }
62 | );
63 |
64 | function logout(): void {
65 | showLogoutDialog();
66 | logoutAction.execute(500);
67 | }
68 |
69 | return reactive({ logout, ...logoutAction });
70 | }
71 |
--------------------------------------------------------------------------------
/apps/mobile/src/common/hooks/use-promise-state.ts:
--------------------------------------------------------------------------------
1 | import { timeout } from '@workspace/shared';
2 | import { reactive, ref, shallowRef } from 'vue';
3 | import { createEventHook, EventHookOn } from '@vueuse/core';
4 |
5 | export interface UsePromiseStateRetrun {
6 | isReady: boolean;
7 | isLoading: boolean;
8 | counter: number;
9 | startTime: number;
10 | endTime: number;
11 | state: TResult;
12 | error: TError;
13 | onError: EventHookOn;
14 | execute: (delay?: number, payload?: TPayload) => Promise;
15 | }
16 |
17 | export function usePromiseState(
18 | promise: (payload?: any) => Promise,
19 | onError?: (e: TError) => void
20 | ): UsePromiseStateRetrun {
21 | const isReady = ref(false);
22 | const isLoading = ref(false);
23 | const counter = ref(0);
24 | const startTime = ref(undefined);
25 | const endTime = ref(undefined);
26 |
27 | const state = shallowRef(undefined);
28 | const error = shallowRef(undefined);
29 |
30 | const errorEvent = createEventHook();
31 | if (onError) errorEvent.on(onError);
32 |
33 | async function execute(delay?: number, payload?: TPayload): Promise {
34 | isReady.value = false;
35 | isLoading.value = true;
36 | startTime.value = Date.now();
37 | endTime.value = undefined;
38 | error.value = undefined;
39 |
40 | try {
41 | if (delay > 0) await timeout(delay);
42 |
43 | const data = await promise(payload);
44 | state.value = data;
45 | isReady.value = true;
46 | counter.value++;
47 | } catch (e) {
48 | error.value = e as TError;
49 | errorEvent.trigger(error.value);
50 | } finally {
51 | isLoading.value = false;
52 | endTime.value = Date.now();
53 | }
54 |
55 | return state.value;
56 | }
57 |
58 | return reactive({
59 | isReady,
60 | isLoading,
61 | counter,
62 | startTime,
63 | endTime,
64 | state,
65 | error,
66 | onError: errorEvent.on,
67 | execute,
68 | });
69 | }
70 |
--------------------------------------------------------------------------------
/apps/mobile/src/common/index.ts:
--------------------------------------------------------------------------------
1 | export * from './api';
2 | export * from './hooks';
3 |
--------------------------------------------------------------------------------
/apps/mobile/src/config.ts:
--------------------------------------------------------------------------------
1 | interface Config {
2 | defaultLocale: string;
3 | useCookies: boolean;
4 | api: {
5 | host: string;
6 | port: number;
7 | };
8 | }
9 |
10 | export const config: Config = {
11 | defaultLocale: import.meta.env.VITE_WEB_DEFAULT_LOCALE ?? 'en',
12 | useCookies: true,
13 |
14 | api: {
15 | host: import.meta.env.VITE_WEB_API_URL ?? 'http://localhost',
16 | port: import.meta.env.VITE_WEB_API_PORT ?? 3000,
17 | },
18 | };
19 |
--------------------------------------------------------------------------------
/apps/mobile/src/locales/en.yml:
--------------------------------------------------------------------------------
1 | app_name: 'nx-nestjs-vue'
2 | app_connection_lost: 'Connection to the server has been lost. Connecting...'
3 |
4 | yes: 'Yes'
5 | no: 'No'
6 | save: 'Save'
7 | add: 'Add'
8 | cancel: 'Cancel'
9 | search: 'Search'
10 | edit: 'Edit'
11 | saved_successfully: 'The changes made have been saved.'
12 |
13 | routes_login: 'Sign in'
14 | routes_dashboard: 'Dashboard'
15 | routes_users: 'Users'
16 | routes_user: 'User'
17 | routes_settings: 'Settings'
18 | routes_logout: 'Sign out'
19 |
20 | validation_field_required: 'This field is required!'
21 | validation_string_min: 'This field must contain at least {min} characters!'
22 | validation_string_max: 'This field must contain a maximum of {max} characters!'
23 | validation_string_email: 'This is not a valid e-mail address!'
24 | validation_string_password_repeat: 'Passwords must match!'
25 | validation_string_matches: 'Incorrect format!'
26 |
27 | roles_user: 'User'
28 | roles_admin: 'Administartor'
29 |
30 | menu_main_title: 'Main menu'
31 | menu_main_caption: 'Basic functions of the application'
32 | menu_admin_title: 'Administration'
33 | menu_admin_caption: 'Functions for administrators'
34 | menu_account_title: 'Account'
35 | menu_account_caption: 'User account options'
36 |
37 | account_session_exp: 'Your session has expired!'
38 | account_logout_success: 'Logged out successfully!'
39 | account_logout_failed: 'There was a problem logging out!'
40 | account_logout_progress: 'Signing out ...'
41 | account_menu_settings: 'Settings'
42 | account_menu_logout: 'Sign out'
43 |
44 | login_welcome_title: 'Control panel'
45 | login_welcome_description: 'Lorem ipsum, dolor sit amet consectetur adipisicing elit. Quaerat quas officiis nisi consequatur optio repudiandae corporis. Quos, iste labore? Laboriosam odio enim deserunt sunt illo commodi nostrum in praesentium suscipit autem, ea deleniti consectetur aspernatur fuga minima obcaecati corrupti iusto aliquid voluptatem! Repellat itaque voluptas facilis dolore expedita? Consectetur, facere!'
46 | login_form_email: 'E-mail adress'
47 | login_form_password: 'Password'
48 | login_form_remember: 'Remember me'
49 | login_form_signin: 'Sign in'
50 | login_form_errors_no_authorization: 'Wrong e-mail or password!'
51 |
52 | dashboard_welcome: 'Hello again, {name}!'
53 |
54 | users_title: 'User settings'
55 | users_email: 'E-mail'
56 | users_form_first_name: 'Name'
57 | users_form_last_name: 'Surname'
58 | users_form_position: 'Position'
59 | users_form_role: 'Role'
60 | users_form_isActive: 'Active'
61 | users_form_password: 'Password'
62 | users_form_errors_default: 'Error occured!'
63 | users_form_errors_samelogin: 'The given username already exists in the database!'
64 | users_create_title: 'New user'
65 | users_create_email: 'E-mail'
66 | users_create_first_name: 'Name'
67 | users_create_last_name: 'Surname'
68 | users_create_position: 'Position'
69 | users_create_role: 'Role'
70 | users_create_password: 'Password'
71 | users_create_errors_default: 'Error occured!'
72 | users_create_errors_user_exists: 'A user with such a login already exists!'
73 | users_list_first_and_last_name: 'Name and surname'
74 | users_list_email: 'E-mail'
75 | users_list_role: 'Role'
76 | users_list_created_at: 'Created at'
77 | users_select_search: 'Search for a user'
78 |
79 | settings_general_title: 'General'
80 | settings_general_role: 'Role'
81 | settings_general_email: 'E-mail'
82 | settings_general_form_first_name: 'Name'
83 | settings_general_form_last_name: 'Surname'
84 | settings_general_form_position: 'Position'
85 | settings_general_form_errors_default: 'Error occured!'
86 | settings_password_title: 'Account'
87 | settings_password_form_password: 'Current password'
88 | settings_password_form_new_password: 'New password'
89 | settings_password_form_repeat_password: 'Repeat new password'
90 | settings_password_form_errors_default: 'Error occured!'
91 | settings_password_form_errors_wrong_password: 'Wrong password!'
92 |
--------------------------------------------------------------------------------
/apps/mobile/src/locales/pl.yml:
--------------------------------------------------------------------------------
1 | app_name: 'nx-nestjs-vue'
2 | app_connection_lost: 'Połączenie z serwerem zostało zerwane. Łączenie...'
3 |
4 | yes: 'Tak'
5 | no: 'Nie'
6 | save: 'Zapisz'
7 | add: 'Dodaj'
8 | cancel: 'Anuluj'
9 | search: 'Wyszukaj'
10 | edit: 'Edycja'
11 | saved_successfully: 'Wprowadzone zmiany zostały zapisane.'
12 |
13 | routes_login: 'Logowanie'
14 | routes_dashboard: 'Dashboard'
15 | routes_users: 'Użytkownicy'
16 | routes_user: 'Użytkownik'
17 | routes_settings: 'Ustawienia'
18 | routes_logout: 'Wyloguj'
19 |
20 | validation_field_required: 'To pole jest wymagane!'
21 | validation_string_min: 'To pole musi zawierać minumum {min} znaków!'
22 | validation_string_max: 'To pole musi zawierać maksymalnie {max} znaków!'
23 | validation_string_email: 'To nie jest poprawny adres e-mail!'
24 | validation_string_password_repeat: 'Hasła muszą być identyczne!'
25 | validation_string_matches: 'Niepoprawny format!'
26 |
27 | roles_user: 'Użytkownik'
28 | roles_admin: 'Administartor'
29 |
30 | menu_main_title: 'Menu główne'
31 | menu_main_caption: 'Podstawowe funkcje aplikacji'
32 | menu_admin_title: 'Administracja'
33 | menu_admin_caption: 'Funkcje dla administratorów'
34 | menu_account_title: 'Konto'
35 | menu_account_caption: 'Opcje konta użytkownika'
36 | account_session_exp: 'Sesja wygasła!'
37 | account_logout_success: 'Wylogowano poprawnie!'
38 | account_logout_failed: 'Wystąpił problem podczas wylogowywania!'
39 | account_logout_progress: 'Wylogowywanie...'
40 | account_menu_settings: 'Ustawienia'
41 | account_menu_logout: 'Wyloguj'
42 |
43 | login_welcome_title: 'Panel kontrolny'
44 | login_welcome_description: 'Lorem ipsum, dolor sit amet consectetur adipisicing elit. Quaerat quas officiis nisi consequatur optio repudiandae corporis. Quos, iste labore? Laboriosam odio enim deserunt sunt illo commodi nostrum in praesentium suscipit autem, ea deleniti consectetur aspernatur fuga minima obcaecati corrupti iusto aliquid voluptatem! Repellat itaque voluptas facilis dolore expedita? Consectetur, facere!'
45 | login_form_login: 'Nazwa użytkownika'
46 | login_form_password: 'Hasło'
47 | login_form_remember: 'Zapamiętaj mnie'
48 | login_form_signin: 'Zaloguj się'
49 | login_form_errors_no_authorization: 'Błędny e-mail lub hasło!'
50 |
51 | dashboard_welcome: 'Witaj ponownie, {name}!'
52 |
53 | users_title: 'Ustawienia użytkownika'
54 | users_email: 'E-mail'
55 | users_form_first_name: 'Imię'
56 | users_form_last_name: 'Nazwisko'
57 | users_form_position: 'Stanowisko'
58 | users_form_role: 'Rola'
59 | users_form_isActive: 'Aktywny'
60 | users_form_password: 'Hasło'
61 | users_form_errors_default: 'Wystąpił błąd!'
62 | users_form_errors_samelogin: 'Podana nazwa użytkownika już istnieje w bazie!'
63 | users_create_title: 'Nowy użytkownik'
64 | users_create_email: 'E-mail'
65 | users_create_first_name: 'Imię'
66 | users_create_last_name: 'Nazwisko'
67 | users_create_position: 'Stanowisko'
68 | users_create_role: 'Rola'
69 | users_create_password: 'Hasło'
70 | users_create_errors_default: 'Wystąpił błąd!'
71 | users_create_errors_user_exists: 'Użytkownik o takim loginie już istnieje!'
72 | users_list_first_and_last_name: 'Imię i nazwisko'
73 | users_list_email: 'E-mail'
74 | users_list_role: 'Rola'
75 | users_list_created_at: 'Utworzony'
76 | users_select_search: 'Wyszukaj użytkownika'
77 |
78 | settings_general_title: 'Ogólne'
79 | settings_general_role: 'Rola'
80 | settings_general_email: 'E-mail'
81 | settings_general_form_first_name: 'Imię'
82 | settings_general_form_last_name: 'Nazwisko'
83 | settings_general_form_position: 'Stanowisko'
84 | settings_general_form_errors_default: 'Wystąpił błąd!'
85 | settings_password_title: 'Konto'
86 | settings_password_form_password: 'Aktualne hasło'
87 | settings_password_form_new_password: 'Nowe hasło'
88 | settings_password_form_repeat_password: 'Powtórz nowe hasło'
89 | settings_password_form_errors_default: 'Wystąpił błąd!'
90 | settings_password_form_errors_wrong_password: 'Błędne hasło!'
--------------------------------------------------------------------------------
/apps/mobile/src/main.ts:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vue';
2 | import { pinia } from './stores';
3 | import router from './router';
4 | import { Quasar, Notify, Dialog } from 'quasar';
5 | import { createI18n } from 'vue-i18n';
6 | import messages from '@intlify/unplugin-vue-i18n/messages';
7 | import { configure as veeConfigure } from 'vee-validate';
8 |
9 | import '@quasar/extras/material-icons/material-icons.css';
10 | import '@quasar/extras/mdi-v6/mdi-v6.css';
11 | import 'quasar/src/css/index.sass';
12 | import './assets/main.scss';
13 |
14 | import App from './app/App.vue';
15 | import { config } from './config';
16 |
17 | veeConfigure({
18 | validateOnInput: true,
19 | });
20 |
21 | const i18n = createI18n({
22 | locale: config.defaultLocale,
23 | legacy: false,
24 | globalInjection: true,
25 | messages,
26 | });
27 |
28 | const app = createApp(App);
29 |
30 | app.use(i18n);
31 | app.use(pinia);
32 | app.use(router);
33 |
34 | app.use(Quasar, {
35 | plugins: {
36 | Notify,
37 | Dialog,
38 | },
39 | });
40 |
41 | app.mount('#app');
42 |
--------------------------------------------------------------------------------
/apps/mobile/src/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DhivinX/nx-nestjs-vue/9efaa801ec6b936aeeff49b7a120ad9b92bae51a/apps/mobile/src/public/favicon.ico
--------------------------------------------------------------------------------
/apps/mobile/src/router/index.ts:
--------------------------------------------------------------------------------
1 | import { Role } from '@workspace/shared';
2 | import { createRouter, createWebHashHistory } from 'vue-router';
3 | import { AuthMeta, useAuthGuard, useLoadingIndicator } from './middlewares';
4 | import { routes } from './routes';
5 |
6 | declare module 'vue-router' {
7 | export interface RouteMeta {
8 | auth: AuthMeta;
9 | roles?: Role[];
10 | }
11 | }
12 |
13 | const router = createRouter({
14 | history: createWebHashHistory(import.meta.env.BASE_URL),
15 | routes,
16 | });
17 |
18 | useAuthGuard(router);
19 | useLoadingIndicator(router);
20 |
21 | export default router;
22 |
--------------------------------------------------------------------------------
/apps/mobile/src/router/middlewares/index.ts:
--------------------------------------------------------------------------------
1 | export * from './use-auth-guard';
2 | export * from './use-loading-indicator';
3 |
--------------------------------------------------------------------------------
/apps/mobile/src/router/middlewares/use-auth-guard.ts:
--------------------------------------------------------------------------------
1 | import { Router } from 'vue-router';
2 | import { useAccountStore } from '@/stores/account';
3 |
4 | export enum AuthMeta {
5 | None,
6 | Required,
7 | Optional,
8 | }
9 |
10 | export function useAuthGuard(router: Router) {
11 | router.beforeEach(async (to) => {
12 | const accountStore = useAccountStore();
13 | accountStore.refreshAuthState();
14 |
15 | if (!accountStore.state.loaded && accountStore.state.authenticated)
16 | await accountStore.fetch(500);
17 |
18 | if (to.meta.auth === AuthMeta.Required && !accountStore.state.authenticated) {
19 | return { name: 'login' };
20 | }
21 |
22 | if (to.meta.auth === AuthMeta.None && accountStore.state.authenticated) {
23 | return { name: 'dashboard' };
24 | }
25 |
26 | if (to.meta.roles && !to.meta.roles.includes(accountStore.state.role)) {
27 | return { name: 'dashboard' };
28 | }
29 |
30 | if (to.name === 'user' && to.params.id === accountStore.state.id) {
31 | return { name: 'dashboard' };
32 | }
33 |
34 | return true;
35 | });
36 | }
37 |
--------------------------------------------------------------------------------
/apps/mobile/src/router/middlewares/use-loading-indicator.ts:
--------------------------------------------------------------------------------
1 | import { Router } from 'vue-router';
2 | import { useAppStore } from '@/stores/app';
3 |
4 | export function useLoadingIndicator(router: Router) {
5 | router.beforeEach(() => {
6 | const appStore = useAppStore();
7 | appStore.state.isRouteLoading = true;
8 | });
9 |
10 | router.afterEach(() => {
11 | const appStore = useAppStore();
12 | appStore.state.isRouteLoading = false;
13 | });
14 | }
15 |
--------------------------------------------------------------------------------
/apps/mobile/src/router/routes.ts:
--------------------------------------------------------------------------------
1 | import { RouteRecordRaw } from 'vue-router';
2 | import { AuthMeta } from './middlewares';
3 |
4 | import DefaultLayout from '@/app/layouts/default.vue';
5 | import AppLayout from '@/app/layouts/app.vue';
6 | import LoginPage from '@/app/pages/login.vue';
7 |
8 | export const routes: RouteRecordRaw[] = [
9 | {
10 | path: '/',
11 | component: DefaultLayout,
12 | children: [
13 | {
14 | path: '/',
15 | name: 'login',
16 | component: LoginPage,
17 | meta: {
18 | auth: AuthMeta.None,
19 | },
20 | },
21 | ],
22 | },
23 | {
24 | path: '/app',
25 | component: AppLayout,
26 | children: [
27 | {
28 | path: '/dashboard',
29 | name: 'dashboard',
30 | component: () => import('@/app/pages/dashboard.vue'),
31 | meta: {
32 | auth: AuthMeta.Required,
33 | },
34 | },
35 | {
36 | path: '/settings',
37 | name: 'settings',
38 | component: () => import('@/app/pages/settings.vue'),
39 | meta: {
40 | auth: AuthMeta.Required,
41 | },
42 | },
43 | ],
44 | },
45 | ];
46 |
--------------------------------------------------------------------------------
/apps/mobile/src/shims-vue.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | declare module '*.vue' {
4 | import { DefineComponent } from 'vue';
5 | // eslint-disable-next-line
6 | const component: DefineComponent<{}, {}, any>;
7 | export default component;
8 | }
9 |
10 | declare module '@intlify/unplugin-vue-i18n/messages' {
11 | import { LocaleMessage } from '@intlify/core-base';
12 | import { VueMessageType } from 'vue-i18n';
13 | const messages: { [x: string]: LocaleMessage };
14 | export default messages;
15 | }
16 |
--------------------------------------------------------------------------------
/apps/mobile/src/stores/app.ts:
--------------------------------------------------------------------------------
1 | import { reactive } from 'vue';
2 | import { useTitle } from '@vueuse/core';
3 | import { defineStore } from 'pinia';
4 | import { watch } from 'vue';
5 | import { useI18n } from 'vue-i18n';
6 | import { useRoute } from 'vue-router';
7 |
8 | interface State {
9 | networkError: boolean;
10 | isRouteLoading: boolean;
11 | routeTitle: string;
12 | }
13 |
14 | export const useAppStore = defineStore('app', () => {
15 | const route = useRoute();
16 | const { t } = useI18n();
17 | const title = useTitle();
18 |
19 | const state: State = reactive({
20 | networkError: false,
21 | isRouteLoading: false,
22 | routeTitle: t(`app_name`),
23 | });
24 |
25 | watch(
26 | () => route.name,
27 | () => {
28 | title.value = t(`routes_${route.name.toString()}`) + ' - ' + t(`app_name`);
29 | state.routeTitle = t(`routes_${route.name.toString()}`);
30 | }
31 | );
32 |
33 | return { state };
34 | });
35 |
--------------------------------------------------------------------------------
/apps/mobile/src/stores/index.ts:
--------------------------------------------------------------------------------
1 | import { createPinia } from 'pinia';
2 |
3 | const pinia = createPinia();
4 |
5 | export { pinia };
6 |
--------------------------------------------------------------------------------
/apps/mobile/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 |
4 | "compilerOptions": {
5 | "outDir": "../../dist/out-tsc",
6 | "types": ["node"],
7 | "composite": true
8 | },
9 |
10 | "include": ["vite.config.*", "vitest.config.*", "cypress.config.*", "../../tsconfig.base.json"]
11 | }
12 |
--------------------------------------------------------------------------------
/apps/mobile/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "compilerOptions": {
4 | "lib": ["esnext", "dom"],
5 | "target": "esnext",
6 | "jsx": "preserve",
7 | "skipLibCheck": true,
8 | "useDefineForClassFields": true,
9 |
10 | "types": [
11 | "vite/client",
12 | "vitest",
13 | "@intlify/unplugin-vue-i18n/messages"
14 | ],
15 |
16 | "baseUrl": ".",
17 | "paths": {
18 | "@/*": ["./src/*"],
19 | "@workspace/shared": ["../../libs/shared/src/index.ts"]
20 | }
21 | },
22 |
23 | "files": [],
24 |
25 | "include": [
26 | "./src/components.d.ts",
27 | "**/*.js",
28 | "**/*.jsx",
29 | "**/*.ts",
30 | "**/*.tsx",
31 | "**/*.vue",
32 | "**/*.json"
33 | ],
34 |
35 | "exclude": ["**/*.spec.js", "**/*.spec.jsx", "**/*.spec.ts", "**/*.spec.tsx"],
36 |
37 | "references": [
38 | {
39 | "path": "./tsconfig.app.json"
40 | },
41 | {
42 | "path": "./tsconfig.spec.json"
43 | }
44 | ]
45 | }
46 |
--------------------------------------------------------------------------------
/apps/mobile/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 |
4 | "compilerOptions": {
5 | "outDir": "../../dist/out-tsc",
6 | "types": ["vitest/globals", "node"],
7 | "composite": true
8 | },
9 |
10 | "include": [
11 | "vite.config.ts",
12 | "**/*.test.ts",
13 | "**/*.spec.ts",
14 | "**/*.test.tsx",
15 | "**/*.spec.tsx",
16 | "**/*.test.js",
17 | "**/*.spec.js",
18 | "**/*.test.jsx",
19 | "**/*.spec.jsx",
20 | "**/*.d.ts"
21 | ]
22 | }
23 |
--------------------------------------------------------------------------------
/apps/mobile/vite.config.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import path from 'path';
4 | import { defineConfig } from 'vite';
5 | import Vue from '@vitejs/plugin-vue';
6 | import Components from 'unplugin-vue-components/vite';
7 | import AutoImport from 'unplugin-auto-import/vite';
8 | import { quasar, transformAssetUrls } from '@quasar/vite-plugin';
9 | import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite';
10 | import checker from 'vite-plugin-checker';
11 | import viteTsConfigPaths from 'vite-tsconfig-paths';
12 |
13 | const resolve = (p: string) => path.resolve(__dirname, p);
14 |
15 | export default defineConfig({
16 | server: {
17 | host: true,
18 | port: 8090,
19 |
20 | fs: {
21 | allow: ['../..'],
22 | },
23 | },
24 |
25 | base: './',
26 | cacheDir: '../../node_modules/.vite-mobile',
27 | clearScreen: true,
28 | assetsInclude: /\.(pdf|jpg|png|svg)$/,
29 |
30 | resolve: {
31 | alias: {
32 | '@/': `${resolve('./src')}/`,
33 | },
34 | },
35 |
36 | publicDir: resolve('./src/public'),
37 |
38 | plugins: [
39 | viteTsConfigPaths({
40 | projects: [resolve('../../tsconfig.base.json')],
41 | }),
42 |
43 | Vue({
44 | template: {
45 | transformAssetUrls,
46 | },
47 | }),
48 |
49 | process.env.VITE_DISABLE_VUE_TSC
50 | ? null
51 | : checker({
52 | vueTsc: true,
53 | }),
54 |
55 | VueI18nPlugin({
56 | defaultSFCLang: 'yml',
57 | include: resolve('./src/locales/**'),
58 | }),
59 |
60 | quasar({
61 | sassVariables: resolve('./src/assets/quasar.scss'),
62 | }),
63 |
64 | AutoImport({
65 | dts: resolve('./src/auto-imports.d.ts'),
66 | imports: ['vue', 'vue-router'],
67 | }),
68 |
69 | Components({
70 | dts: resolve('./src/components.d.ts'),
71 | dirs: ['src/app/components'],
72 | }),
73 | ],
74 |
75 | test: {
76 | globals: true,
77 | cache: {
78 | dir: '../../node_modules/.vitest',
79 | },
80 | environment: 'jsdom',
81 | include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
82 | },
83 | });
84 |
--------------------------------------------------------------------------------
/apps/server/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["../../.eslintrc.json"]
3 | }
4 |
--------------------------------------------------------------------------------
/apps/server/jest.config.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | export default {
3 | displayName: 'server',
4 | preset: '../../jest.preset.js',
5 |
6 | globals: {
7 | 'ts-jest': {
8 | tsconfig: '/tsconfig.spec.json',
9 | },
10 | },
11 |
12 | testEnvironment: 'node',
13 |
14 | transform: {
15 | '^.+\\.[tj]s$': 'ts-jest',
16 | },
17 |
18 | moduleFileExtensions: ['ts', 'js', 'html'],
19 | coverageDirectory: '../../coverage/apps/server',
20 | };
21 |
--------------------------------------------------------------------------------
/apps/server/project.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "server",
3 | "$schema": "https://raw.githubusercontent.com/nrwl/nx/master/packages/nx/schemas/project-schema.json",
4 | "sourceRoot": "apps/server/src",
5 | "projectType": "application",
6 | "targets": {
7 | "build": {
8 | "executor": "@nx/webpack:webpack",
9 | "outputs": ["{options.outputPath}"],
10 | "options": {
11 | "target": "node",
12 | "compiler": "tsc",
13 | "isolatedConfig": true,
14 | "webpackConfig": "apps/server/webpack.config.js",
15 | "outputPath": "dist/apps/server",
16 | "main": "apps/server/src/main.ts",
17 | "tsConfig": "apps/server/tsconfig.app.json",
18 | "assets": ["apps/server/src/assets", "apps/server/src/static"],
19 | "additionalEntryPoints": [
20 | {
21 | "entryName": "cli",
22 | "entryPath": "src/cli.ts"
23 | }
24 | ]
25 | },
26 | "configurations": {
27 | "production": {
28 | "optimization": true,
29 | "extractLicenses": true,
30 | "inspect": false
31 | }
32 | }
33 | },
34 | "build-cli": {
35 | "executor": "@nx/webpack:webpack",
36 | "outputs": ["{options.outputPath}"],
37 | "options": {
38 | "target": "node",
39 | "compiler": "tsc",
40 | "isolatedConfig": true,
41 | "webpackConfig": "apps/server/webpack.config.js",
42 | "outputPath": "dist/apps/server-cli",
43 | "main": "apps/server/src/cli.ts",
44 | "tsConfig": "apps/server/tsconfig.app.json",
45 | "assets": ["apps/server/src/assets"]
46 | },
47 | "configurations": {
48 | "production": {
49 | "optimization": true,
50 | "extractLicenses": true,
51 | "inspect": false
52 | }
53 | }
54 | },
55 | "serve": {
56 | "executor": "@nx/js:node",
57 | "options": {
58 | "buildTarget": "server:build"
59 | }
60 | },
61 | "lint": {
62 | "executor": "@nx/linter:eslint",
63 | "outputs": ["{options.outputFile}"],
64 | "options": {
65 | "lintFilePatterns": ["apps/server/**/*.ts"],
66 | "fix": true
67 | }
68 | },
69 | "test": {
70 | "executor": "@nx/jest:jest",
71 | "outputs": ["coverage/apps/server"],
72 | "options": {
73 | "jestConfig": "apps/server/jest.config.ts",
74 | "passWithNoTests": true
75 | }
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/apps/server/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { staticDir } from '@/common/filesystem';
2 | import { Module } from '@nestjs/common';
3 | import { ConfigModule } from '@nestjs/config';
4 | import { ServeStaticModule } from '@nestjs/serve-static';
5 | import { config } from '@/common';
6 | import { DatabaseModule } from './db/database.module';
7 | import { CliModule } from './cli/cli.module';
8 | import { CronModule } from './cron/cron.module';
9 | import { UsersModule } from './users/users.module';
10 | import { AuthModule } from './auth/auth.module';
11 |
12 | @Module({
13 | imports: [
14 | ConfigModule.forRoot({
15 | load: [config],
16 | isGlobal: true,
17 | }),
18 |
19 | ServeStaticModule.forRoot({
20 | rootPath: staticDir(),
21 | exclude: ['/api*'],
22 | }),
23 |
24 | DatabaseModule,
25 | CliModule,
26 | CronModule,
27 | AuthModule,
28 | UsersModule,
29 | ],
30 |
31 | controllers: [],
32 | })
33 | export class AppModule {}
34 |
--------------------------------------------------------------------------------
/apps/server/src/app/auth/auth.controller.ts:
--------------------------------------------------------------------------------
1 | import { AuthLoginDto } from '@workspace/shared';
2 | import { Body, Controller, Post, Req, Res } from '@nestjs/common';
3 | import { AuthService } from './auth.service';
4 | import { Response, Request } from 'express';
5 | import { AuthSession } from './decorators/auth-session.decorator';
6 | import { AuthLoginResponse } from '@workspace/shared';
7 | import { Session } from '@/app/db/session.entity';
8 | import { Public } from './decorators/public.decorator';
9 |
10 | @Controller('/auth')
11 | export class AuthController {
12 | constructor(private readonly authService: AuthService) {}
13 |
14 | @Public()
15 | @Post('/login')
16 | async login(
17 | @Req() request: Request,
18 | @Res({ passthrough: true }) response: Response,
19 | @AuthSession() session: Session,
20 | @Body() authLoginDto: AuthLoginDto
21 | ): Promise {
22 | return this.authService.login(request, response, session, authLoginDto);
23 | }
24 |
25 | @Post('/logout')
26 | async logout(
27 | @AuthSession() session: Session,
28 | @Req() request: Request,
29 | @Res({ passthrough: true }) response: Response
30 | ): Promise {
31 | return this.authService.logout(request, response, session);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/apps/server/src/app/auth/auth.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common';
2 | import { APP_GUARD } from '@nestjs/core';
3 | import { UsersModule } from '@/app/users/users.module';
4 | import { AuthController } from './auth.controller';
5 | import { AuthService } from './auth.service';
6 | import { JwtAuthGuard } from './guards/jwt-auth.guard';
7 | import { JwtStrategy } from './strategies/jwt.strategy';
8 | import { RolesGuard } from './guards/roles.guard';
9 |
10 | @Module({
11 | imports: [UsersModule],
12 | controllers: [AuthController],
13 | providers: [
14 | AuthService,
15 | JwtStrategy,
16 | {
17 | provide: APP_GUARD,
18 | useClass: JwtAuthGuard,
19 | },
20 | {
21 | provide: APP_GUARD,
22 | useClass: RolesGuard,
23 | },
24 | ],
25 | exports: [AuthService, JwtStrategy],
26 | })
27 | export class AuthModule {}
28 |
--------------------------------------------------------------------------------
/apps/server/src/app/auth/decorators/auth-session.decorator.ts:
--------------------------------------------------------------------------------
1 | import { Session } from '@/app/db/session.entity';
2 | import { createParamDecorator, ExecutionContext } from '@nestjs/common';
3 |
4 | export const AuthSession = createParamDecorator((_data, context: ExecutionContext): Session => {
5 | return context.switchToHttp().getRequest().userSession;
6 | });
7 |
--------------------------------------------------------------------------------
/apps/server/src/app/auth/decorators/auth-user.decorator.ts:
--------------------------------------------------------------------------------
1 | import { User } from '@/app/db/user.entity';
2 | import { createParamDecorator, ExecutionContext } from '@nestjs/common';
3 |
4 | export const AuthUser = createParamDecorator((_data, context: ExecutionContext): User => {
5 | const session = context.switchToHttp().getRequest().userSession;
6 | if (!session) return undefined;
7 | return session.user;
8 | });
9 |
--------------------------------------------------------------------------------
/apps/server/src/app/auth/decorators/public.decorator.ts:
--------------------------------------------------------------------------------
1 | import { SetMetadata } from '@nestjs/common';
2 |
3 | export const IS_PUBLIC_KEY = 'isPublic';
4 | export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);
5 |
--------------------------------------------------------------------------------
/apps/server/src/app/auth/decorators/roles.decorator.ts:
--------------------------------------------------------------------------------
1 | import { Role } from '@workspace/shared';
2 | import { SetMetadata } from '@nestjs/common';
3 |
4 | export const ROLES_KEY = 'roles';
5 | export const Roles = (...roles: Role[]) => SetMetadata(ROLES_KEY, roles);
6 |
--------------------------------------------------------------------------------
/apps/server/src/app/auth/guards/jwt-auth.guard.ts:
--------------------------------------------------------------------------------
1 | import { Injectable, UnauthorizedException } from '@nestjs/common';
2 | import { Reflector } from '@nestjs/core';
3 | import { AuthGuard } from '@nestjs/passport';
4 | import { IS_PUBLIC_KEY } from '../decorators/public.decorator';
5 |
6 | @Injectable()
7 | export class JwtAuthGuard extends AuthGuard('jwt') {
8 | constructor(private reflector: Reflector) {
9 | super({
10 | property: 'userSession',
11 | });
12 | }
13 |
14 | handleRequest(err, userSession, _info, context) {
15 | const isPublic = this.reflector.getAllAndOverride(IS_PUBLIC_KEY, [
16 | context.getHandler(),
17 | context.getClass(),
18 | ]);
19 |
20 | if ((err || !userSession) && !isPublic) {
21 | throw err || new UnauthorizedException();
22 | }
23 |
24 | return userSession;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/apps/server/src/app/auth/guards/roles.guard.ts:
--------------------------------------------------------------------------------
1 | import { Role } from '@workspace/shared';
2 | import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
3 | import { Reflector } from '@nestjs/core';
4 | import { ROLES_KEY } from '../decorators/roles.decorator';
5 |
6 | @Injectable()
7 | export class RolesGuard implements CanActivate {
8 | constructor(private reflector: Reflector) {}
9 |
10 | canActivate(context: ExecutionContext): boolean {
11 | const requiredRoles = this.reflector.getAllAndOverride(ROLES_KEY, [
12 | context.getHandler(),
13 | context.getClass(),
14 | ]);
15 |
16 | if (!requiredRoles) return true;
17 |
18 | const user = context.switchToHttp().getRequest().userSession.user;
19 | return requiredRoles.some((role) => user.role === role);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/apps/server/src/app/auth/strategies/jwt.strategy.ts:
--------------------------------------------------------------------------------
1 | import { Strategy } from 'passport-jwt';
2 | import { Injectable, UnauthorizedException } from '@nestjs/common';
3 | import { PassportStrategy } from '@nestjs/passport';
4 | import { ConfigService } from '@nestjs/config';
5 | import { Session } from '@/app/db/session.entity';
6 |
7 | export interface JwtPayload {
8 | id: string;
9 | }
10 |
11 | function tokenExtractor(req: any): null | string {
12 | const bearerToken =
13 | req.headers.authorization && req.headers.authorization.includes('Bearer ')
14 | ? req.headers.authorization.replace('Bearer ', '')
15 | : null;
16 |
17 | if (bearerToken) return bearerToken;
18 | else return req && req.cookies ? (req.cookies?.jwt as string) ?? null : null;
19 | }
20 |
21 | @Injectable()
22 | export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
23 | constructor(configService: ConfigService) {
24 | super({
25 | jwtFromRequest: tokenExtractor,
26 | secretOrKey: configService.get('secrets.jwt'),
27 | });
28 | }
29 |
30 | async validate(payload: JwtPayload) {
31 | if (!payload || !payload.id) {
32 | throw new UnauthorizedException();
33 | }
34 |
35 | const userSession = await Session.findOne({
36 | relations: ['user'],
37 |
38 | where: {
39 | token: payload.id,
40 | },
41 | });
42 |
43 | if (!userSession) {
44 | throw new UnauthorizedException();
45 | }
46 |
47 | userSession.lastSeen = new Date();
48 | userSession.save();
49 |
50 | return userSession;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/apps/server/src/app/auth/utils/hash-password.ts:
--------------------------------------------------------------------------------
1 | import crypto from 'crypto';
2 |
3 | export function hashPassword(plain: string, key: string): string {
4 | const hmac = crypto.createHmac('sha512', key);
5 | hmac.update(plain);
6 | return hmac.digest('hex');
7 | }
8 |
--------------------------------------------------------------------------------
/apps/server/src/app/cli/cli.module.ts:
--------------------------------------------------------------------------------
1 | import { UsersModule } from '@/app/users/users.module';
2 | import { Module } from '@nestjs/common';
3 | import { SeedCommand } from './seed.command';
4 |
5 | @Module({
6 | imports: [UsersModule],
7 | providers: [SeedCommand],
8 | })
9 | export class CliModule {}
10 |
--------------------------------------------------------------------------------
/apps/server/src/app/cli/seed.command.ts:
--------------------------------------------------------------------------------
1 | import { Command, CommandRunner } from 'nest-commander';
2 | import { UsersService } from '@/app/users/users.service';
3 | import { ConflictException, Logger } from '@nestjs/common';
4 | import { usersSeedData } from './seed/seed-data';
5 | import { timeout, UserCreateDto } from '@workspace/shared';
6 |
7 | @Command({
8 | name: 'seed',
9 | description: 'Seed initial data to database',
10 | })
11 | export class SeedCommand extends CommandRunner {
12 | private readonly logger = new Logger(SeedCommand.name);
13 |
14 | constructor(private readonly usersService: UsersService) {
15 | super();
16 | }
17 |
18 | async run(): Promise {
19 | await this.createTestUsers(usersSeedData);
20 | }
21 |
22 | async createTestUsers(users: UserCreateDto[]) {
23 | for (const _user of users) {
24 | this.logger.verbose(`Creating account: ${_user.email}`);
25 | await timeout(200);
26 |
27 | try {
28 | const user = await this.usersService.createOne(_user);
29 | this.logger.verbose(`Account has been created! ID: ${user.id}`);
30 | } catch (e) {
31 | if (e instanceof ConflictException) {
32 | this.logger.error('The account already exists');
33 | } else {
34 | this.logger.error(e);
35 | }
36 | }
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/apps/server/src/app/cli/seed/seed-data.ts:
--------------------------------------------------------------------------------
1 | import { Role, UserCreateDto } from '@workspace/shared';
2 |
3 | export const usersSeedData: UserCreateDto[] = [
4 | {
5 | email: 'admin@admin.com',
6 | password: '123456',
7 | role: Role.Admin,
8 | firstName: 'John',
9 | lastName: 'Doe',
10 | },
11 | ];
12 |
--------------------------------------------------------------------------------
/apps/server/src/app/cron/cron.module.ts:
--------------------------------------------------------------------------------
1 | import { ScheduleModule } from '@nestjs/schedule';
2 | import { Module } from '@nestjs/common';
3 | import { CronService } from './cron.service';
4 | import { AuthModule } from '@/app/auth/auth.module';
5 |
6 | @Module({
7 | imports: [ScheduleModule.forRoot(), AuthModule],
8 | providers: [CronService],
9 | })
10 | export class CronModule {}
11 |
--------------------------------------------------------------------------------
/apps/server/src/app/cron/cron.service.ts:
--------------------------------------------------------------------------------
1 | import { AuthService } from '@/app/auth/auth.service';
2 | import { Injectable, Logger } from '@nestjs/common';
3 | import { Cron, CronExpression } from '@nestjs/schedule';
4 |
5 | @Injectable()
6 | export class CronService {
7 | private readonly logger = new Logger(CronService.name);
8 |
9 | constructor(private readonly authService: AuthService) {}
10 |
11 | @Cron(CronExpression.EVERY_DAY_AT_MIDNIGHT)
12 | async cleanExpiredSessions() {
13 | const affected = await this.authService.cleanExpiredSessions();
14 | this.logger.log(`Cleaned up ${affected} expired user sessions`);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/apps/server/src/app/db/database.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common';
2 | import { ConfigModule, ConfigService } from '@nestjs/config';
3 | import { TypeOrmModule, TypeOrmModuleOptions } from '@nestjs/typeorm';
4 | import { Session } from './session.entity';
5 | import { User } from './user.entity';
6 |
7 | const entities: unknown[] = [User, Session];
8 |
9 | @Module({
10 | imports: [
11 | TypeOrmModule.forRootAsync({
12 | imports: [ConfigModule],
13 | inject: [ConfigService],
14 |
15 | useFactory: async (configService: ConfigService) => {
16 | return {
17 | type: configService.get('db.type'),
18 | host: configService.get('db.host'),
19 | port: configService.get('db.port'),
20 | database: configService.get('db.name'),
21 | username: configService.get('db.user'),
22 | password: configService.get('db.password'),
23 | synchronize: configService.get('db.synchronize'),
24 | bigNumberStrings: false,
25 | entities: entities,
26 | } as TypeOrmModuleOptions;
27 | },
28 | }),
29 | ],
30 |
31 | controllers: [],
32 | providers: [],
33 | })
34 | export class DatabaseModule {}
35 |
--------------------------------------------------------------------------------
/apps/server/src/app/db/session.entity.ts:
--------------------------------------------------------------------------------
1 | import {
2 | BaseEntity,
3 | Column,
4 | Entity,
5 | Index,
6 | JoinColumn,
7 | ManyToOne,
8 | PrimaryGeneratedColumn,
9 | } from 'typeorm';
10 | import { User } from './user.entity';
11 |
12 | @Entity({ name: 'sessions' })
13 | export class Session extends BaseEntity {
14 | @PrimaryGeneratedColumn('uuid')
15 | id: string;
16 |
17 | @ManyToOne(() => User, (user) => user.sessions, { nullable: false })
18 | @JoinColumn()
19 | @Index()
20 | user: User;
21 |
22 | @Column({
23 | nullable: true,
24 | default: null,
25 | })
26 | token: string;
27 |
28 | @Column({
29 | type: 'bigint',
30 | nullable: true,
31 | default: null,
32 | })
33 | exp: number;
34 |
35 | @Column({
36 | default: false,
37 | })
38 | cookies: boolean;
39 |
40 | @Column({
41 | default: () => 'CURRENT_TIMESTAMP',
42 | })
43 | lastSeen: Date;
44 |
45 | @Column({
46 | default: () => 'CURRENT_TIMESTAMP',
47 | })
48 | createdAt: Date;
49 | }
50 |
--------------------------------------------------------------------------------
/apps/server/src/app/db/user.entity.ts:
--------------------------------------------------------------------------------
1 | import { Role } from '@workspace/shared';
2 | import { BaseEntity, Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm';
3 | import { Session } from './session.entity';
4 |
5 | @Entity({ name: 'users' })
6 | export class User extends BaseEntity {
7 | @PrimaryGeneratedColumn('uuid')
8 | id: string;
9 |
10 | @OneToMany(() => Session, (session) => session.user)
11 | sessions: Session[];
12 |
13 | @Column({
14 | type: 'enum',
15 | enum: Role,
16 | default: Role.User,
17 | })
18 | role: Role;
19 |
20 | @Column({
21 | length: 255,
22 | })
23 | email: string;
24 |
25 | @Column()
26 | hash: string;
27 |
28 | @Column()
29 | firstName: string;
30 |
31 | @Column()
32 | lastName: string;
33 |
34 | @Column({
35 | nullable: true,
36 | })
37 | position: string;
38 |
39 | @Column({
40 | default: true,
41 | })
42 | isActive: boolean;
43 |
44 | @Column({
45 | default: () => 'CURRENT_TIMESTAMP',
46 | })
47 | createdAt: Date;
48 | }
49 |
--------------------------------------------------------------------------------
/apps/server/src/app/users/users.controller.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Body,
3 | Controller,
4 | Delete,
5 | ForbiddenException,
6 | Get,
7 | Param,
8 | ParseUUIDPipe,
9 | Post,
10 | Put,
11 | Query,
12 | } from '@nestjs/common';
13 | import { UsersService } from './users.service';
14 | import { AuthUser } from '@/app/auth/decorators/auth-user.decorator';
15 | import { User } from '@/app/db/user.entity';
16 | import {
17 | UserCreateDto,
18 | UserProfileResponse,
19 | UserUpdateDto,
20 | UserUpdateSelfDto,
21 | UserUpdateSelfPasswordDto,
22 | PaginationResponse,
23 | PaginationDto,
24 | } from '@workspace/shared';
25 |
26 | @Controller('/users')
27 | export class UsersController {
28 | constructor(private readonly usersService: UsersService) {}
29 |
30 | @Get('/self')
31 | async getSelf(@AuthUser() user: User) {
32 | return this.usersService.getProfile(user);
33 | }
34 |
35 | @Put('/self')
36 | async updateSelf(
37 | @AuthUser() user: User,
38 | @Body() userUpdateSelfDto: UserUpdateSelfDto
39 | ): Promise {
40 | return await this.usersService.updateSelf(user, userUpdateSelfDto);
41 | }
42 |
43 | @Put('/self/password')
44 | async updateSelfPassword(
45 | @AuthUser() user: User,
46 | @Body() userUpdateSelfPasswordDto: UserUpdateSelfPasswordDto
47 | ): Promise {
48 | return await this.usersService.updateSelfPassword(user, userUpdateSelfPasswordDto);
49 | }
50 |
51 | @Post('/')
52 | async createOne(@Body() userCreateDto: UserCreateDto): Promise {
53 | return await this.usersService.createOne(userCreateDto);
54 | }
55 |
56 | @Get('/')
57 | async getMany(
58 | @Query() paginationDto: PaginationDto
59 | ): Promise> {
60 | return await this.usersService.getMany(paginationDto);
61 | }
62 |
63 | @Get('/:id')
64 | async getOne(@Param('id', ParseUUIDPipe) id: string): Promise {
65 | return await this.usersService.getOne(id);
66 | }
67 |
68 | @Put('/:id')
69 | async updateOne(
70 | @AuthUser() user: User,
71 | @Param('id', ParseUUIDPipe) id: string,
72 | @Body() userUpdateDto: UserUpdateDto
73 | ): Promise {
74 | if (id === user.id) throw new ForbiddenException();
75 | return await this.usersService.updateOne(id, userUpdateDto);
76 | }
77 |
78 | @Delete('/:id')
79 | async deleteOne(@Param('id', ParseUUIDPipe) id: string): Promise {
80 | return await this.usersService.deleteOne(id);
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/apps/server/src/app/users/users.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common';
2 | import { UsersController } from './users.controller';
3 | import { UsersService } from './users.service';
4 |
5 | @Module({
6 | controllers: [UsersController],
7 | providers: [UsersService],
8 | exports: [UsersService],
9 | })
10 | export class UsersModule {}
11 |
--------------------------------------------------------------------------------
/apps/server/src/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DhivinX/nx-nestjs-vue/9efaa801ec6b936aeeff49b7a120ad9b92bae51a/apps/server/src/assets/.gitkeep
--------------------------------------------------------------------------------
/apps/server/src/cli.ts:
--------------------------------------------------------------------------------
1 | import { CommandFactory } from 'nest-commander';
2 | import { AppModule } from './app/app.module';
3 | import 'multer';
4 |
5 | async function bootstrap() {
6 | await CommandFactory.run(AppModule, ['verbose', 'error', 'warn']);
7 | }
8 |
9 | bootstrap();
10 |
--------------------------------------------------------------------------------
/apps/server/src/common/config.ts:
--------------------------------------------------------------------------------
1 | import { object, number, string, boolean, ValidationError, array } from 'yup';
2 |
3 | const configSchema = object().shape({
4 | http: object({
5 | port: number().nullable().optional().default(3000),
6 | ssl: boolean().nullable().optional().default(false),
7 | key: string().nullable().optional(),
8 | cert: string().nullable().optional(),
9 | cors: array()
10 | .nullable()
11 | .optional()
12 | .default(['http://localhost'])
13 | .transform((_, v) => {
14 | return JSON.parse(v);
15 | })
16 | .of(string()),
17 | }),
18 |
19 | db: object({
20 | type: string().nullable().optional().default('postgres'),
21 | host: string().nullable().optional().default('localhost'),
22 | port: number().nullable().optional().default(3000),
23 | name: string().required(),
24 | user: string().required(),
25 | password: string().required(),
26 | synchronize: boolean().nullable().optional().default(true),
27 | }),
28 |
29 | secrets: object({
30 | pwdsalt: string().required(),
31 | jwt: string().required(),
32 | }),
33 | });
34 |
35 | export const config = () => {
36 | try {
37 | const _config = {
38 | http: {
39 | port: process.env.NEST_API_HTTP_PORT,
40 | ssl: process.env.NEST_API_HTTP_SSL,
41 | key: process.env.NEST_API_HTTP_KEY,
42 | cert: process.env.NEST_API_HTTP_CERT,
43 | cors: process.env.NEST_API_HTTP_CORS,
44 | },
45 |
46 | db: {
47 | type: process.env.DATABASE_TYPE,
48 | host: process.env.DATABASE_HOST,
49 | port: process.env.DATABASE_PORT,
50 |
51 | name: process.env.DATABASE_NAME,
52 | user: process.env.DATABASE_USER,
53 | password: process.env.DATABASE_PASSWORD,
54 | synchronize: process.env.DATABASE_ENABLE_SYNC,
55 | },
56 |
57 | secrets: {
58 | pwdsalt: process.env.NEST_API_SECRETS_PWDSALT,
59 | jwt: process.env.NEST_API_SECRETS_JWT,
60 | },
61 | };
62 |
63 | return configSchema.validateSync(_config);
64 | } catch (err) {
65 | throw new Error((err as ValidationError).message);
66 | }
67 | };
68 |
--------------------------------------------------------------------------------
/apps/server/src/common/filesystem.ts:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 |
3 | interface NodeProcess extends NodeJS.Process {
4 | pkg?: {
5 | entrypoint: string;
6 | defaultEntrypoint: string;
7 | };
8 | }
9 |
10 | export function realDirname(): string {
11 | if ((process as NodeProcess).pkg) return process.cwd();
12 | return __dirname;
13 | }
14 |
15 | export function assetsDir(): string {
16 | return path.join(realDirname(), 'assets');
17 | }
18 |
19 | export function staticDir(): string {
20 | return path.join(realDirname(), 'static');
21 | }
22 |
--------------------------------------------------------------------------------
/apps/server/src/common/index.ts:
--------------------------------------------------------------------------------
1 | export * from './config';
2 | export * from './pipes/yup-validation.pipe';
3 |
--------------------------------------------------------------------------------
/apps/server/src/common/pipes/yup-validation.pipe.ts:
--------------------------------------------------------------------------------
1 | import { ArgumentMetadata, Injectable, PipeTransform, BadRequestException } from '@nestjs/common';
2 | import { ValidationError } from 'yup';
3 |
4 | interface Error {
5 | path: string;
6 | message: string;
7 | }
8 |
9 | const serializeValidationError = (err: ValidationError) => {
10 | const invalid: Error[] = err.inner.map(({ path, message }) => ({
11 | path,
12 | message,
13 | }));
14 |
15 | return invalid;
16 | };
17 |
18 | @Injectable()
19 | export class YupValidationPipe implements PipeTransform {
20 | async transform(value: any, { metatype }: ArgumentMetadata) {
21 | const { schema } = metatype.prototype;
22 | if (!schema) return value;
23 |
24 | try {
25 | return await schema.validate(value, { abortEarly: false });
26 | } catch (err) {
27 | throw new BadRequestException(serializeValidationError(err as ValidationError));
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/apps/server/src/main.ts:
--------------------------------------------------------------------------------
1 | import express from 'express';
2 | import * as http from 'http';
3 | import * as https from 'https';
4 | import fs from 'fs';
5 | import { NestFactory } from '@nestjs/core';
6 | import { ConfigService } from '@nestjs/config';
7 | import cookieParser from 'cookie-parser';
8 | import helmet from 'helmet';
9 | import { AppModule } from './app/app.module';
10 | import { YupValidationPipe } from './common';
11 | import { ExpressAdapter } from '@nestjs/platform-express';
12 | import { Logger } from '@nestjs/common';
13 |
14 | async function bootstrap() {
15 | const server = express();
16 | const app = await NestFactory.create(AppModule, new ExpressAdapter(server));
17 |
18 | const configService = app.get(ConfigService);
19 | const logger = new Logger('APP');
20 |
21 | const port = configService.get('http.port');
22 | const origins = configService.get('http.cors');
23 | const secure = configService.get('http.secure');
24 | const keyPath = configService.get('http.key');
25 | const certPath = configService.get('http.cert');
26 |
27 | app.setGlobalPrefix('/api');
28 |
29 | app.enableCors({
30 | origin: origins,
31 | credentials: true,
32 | });
33 |
34 | app.use(
35 | helmet.contentSecurityPolicy({
36 | useDefaults: true,
37 | directives: {
38 | 'img-src': [`'self'`, 'https: data:'],
39 | },
40 | })
41 | );
42 |
43 | app.use(cookieParser());
44 | app.useGlobalPipes(new YupValidationPipe());
45 |
46 | await app.init();
47 |
48 | if (secure) {
49 | try {
50 | const httpsOptions = {
51 | key: fs.readFileSync(keyPath),
52 | cert: fs.readFileSync(certPath),
53 | };
54 |
55 | https.createServer(httpsOptions, server).listen(port);
56 |
57 | logger.log(
58 | `🚀 The server was started in \x1b[36mHTTPS\x1b[32m mode on port \x1b[36m${port}`
59 | );
60 | } catch (e) {
61 | logger.error(`The HTTPS server cannot be started`);
62 | logger.error(e);
63 | }
64 | } else {
65 | http.createServer(server).listen(configService.get('http.port'));
66 |
67 | logger.log(
68 | `🚀 The server was started in \x1b[35mHTTP\x1b[32m mode on port \x1b[36m${port}`
69 | );
70 | }
71 | }
72 |
73 | bootstrap();
74 |
--------------------------------------------------------------------------------
/apps/server/src/static/index.html:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DhivinX/nx-nestjs-vue/9efaa801ec6b936aeeff49b7a120ad9b92bae51a/apps/server/src/static/index.html
--------------------------------------------------------------------------------
/apps/server/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 |
4 | "compilerOptions": {
5 | "outDir": "../../dist/out-tsc",
6 | "module": "commonjs",
7 | "types": ["node"]
8 | },
9 |
10 | "exclude": ["jest.config.ts", "**/*.spec.ts", "**/*.test.ts"],
11 | "include": ["**/*.ts"]
12 | }
13 |
--------------------------------------------------------------------------------
/apps/server/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 |
4 | "compilerOptions": {
5 | "baseUrl": ".",
6 |
7 | "paths": {
8 | "@/*": ["./src/*"],
9 | "@workspace/shared": ["../../libs/shared/src/index.ts"]
10 | }
11 | },
12 |
13 | "files": [],
14 | "include": [],
15 | "references": [
16 | {
17 | "path": "./tsconfig.app.json"
18 | },
19 | {
20 | "path": "./tsconfig.spec.json"
21 | }
22 | ]
23 | }
24 |
--------------------------------------------------------------------------------
/apps/server/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../../dist/out-tsc",
5 | "module": "commonjs",
6 | "types": ["jest", "node"]
7 | },
8 | "include": ["jest.config.ts", "**/*.test.ts", "**/*.spec.ts", "**/*.d.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/apps/server/webpack.config.js:
--------------------------------------------------------------------------------
1 | const { composePlugins, withNx } = require('@nx/webpack');
2 |
3 | // Nx plugins for webpack.
4 | module.exports = composePlugins(withNx(), (config) => {
5 | // Update the webpack config as needed here.
6 | // e.g. `config.plugins.push(new MyPlugin())`
7 | return config;
8 | });
9 |
--------------------------------------------------------------------------------
/apps/web/.build.env:
--------------------------------------------------------------------------------
1 | VITE_DISABLE_VUE_TSC=true
--------------------------------------------------------------------------------
/apps/web/.electron.env:
--------------------------------------------------------------------------------
1 | VITE_IS_ELECTRON_APP=true
--------------------------------------------------------------------------------
/apps/web/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "../../.eslintrc.json"
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/apps/web/electron/electron-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | declare namespace NodeJS {
4 | interface ProcessEnv {
5 | DIST: string;
6 | /** /dist/ or /public/ */
7 | PUBLIC: string;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/apps/web/electron/main/index.ts:
--------------------------------------------------------------------------------
1 | process.env.DIST = join(__dirname, '../..');
2 | console.log(process.env.DIST);
3 | process.env.PUBLIC = app.isPackaged
4 | ? process.env.DIST
5 | : join(__dirname, '../../../../../apps/web/src/public');
6 |
7 | import { app, BrowserWindow, shell, ipcMain } from 'electron';
8 | import { release } from 'os';
9 | import { join } from 'path';
10 |
11 | // Disable GPU Acceleration for Windows 7
12 | if (release().startsWith('6.1')) app.disableHardwareAcceleration();
13 |
14 | // Set application name for Windows 10+ notifications
15 | if (process.platform === 'win32') app.setAppUserModelId(app.getName());
16 |
17 | if (!app.requestSingleInstanceLock()) {
18 | app.quit();
19 | process.exit(0);
20 | }
21 |
22 | // Remove electron security warnings
23 | // This warning only shows in development mode
24 | // Read more on https://www.electronjs.org/docs/latest/tutorial/security
25 | process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true';
26 |
27 | let win: BrowserWindow | null = null;
28 | // Here, you can also use other preload
29 | const preload = join(__dirname, '../preload/index.js');
30 | const url = process.env.VITE_DEV_SERVER_URL as string;
31 | const indexHtml = join(process.env.DIST, 'index.html');
32 |
33 | async function createWindow() {
34 | win = new BrowserWindow({
35 | width: 1280,
36 | height: 800,
37 | autoHideMenuBar: true,
38 | icon: join(process.env.PUBLIC, 'favicon.ico'),
39 | webPreferences: {
40 | preload,
41 | // Warning: Enable nodeIntegration and disable contextIsolation is not secure in production
42 | // Consider using contextBridge.exposeInMainWorld
43 | // Read more on https://www.electronjs.org/docs/latest/tutorial/context-isolation
44 | nodeIntegration: true,
45 | contextIsolation: false,
46 | },
47 | });
48 |
49 | if (app.isPackaged) {
50 | win.loadFile(indexHtml);
51 | } else {
52 | win.loadURL(url);
53 | // Open devTool if the app is not packaged
54 | win.webContents.openDevTools();
55 | }
56 |
57 | // Test actively push message to the Electron-Renderer
58 | win.webContents.on('did-finish-load', () => {
59 | win?.webContents.send('main-process-message', new Date().toLocaleString());
60 | });
61 |
62 | // Make all links open with the browser, not with the application
63 | win.webContents.setWindowOpenHandler(({ url }) => {
64 | if (url.startsWith('https:')) shell.openExternal(url);
65 | return { action: 'deny' };
66 | });
67 | }
68 |
69 | app.whenReady().then(createWindow);
70 |
71 | app.on('window-all-closed', () => {
72 | win = null;
73 | if (process.platform !== 'darwin') app.quit();
74 | });
75 |
76 | app.on('second-instance', () => {
77 | if (win) {
78 | // Focus on the main window if the user tried to open another
79 | if (win.isMinimized()) win.restore();
80 | win.focus();
81 | }
82 | });
83 |
84 | app.on('activate', () => {
85 | const allWindows = BrowserWindow.getAllWindows();
86 | if (allWindows.length) {
87 | allWindows[0].focus();
88 | } else {
89 | createWindow();
90 | }
91 | });
92 |
93 | // new window example arg: new windows url
94 | ipcMain.handle('open-win', (_event, arg) => {
95 | const childWindow = new BrowserWindow({
96 | webPreferences: {
97 | preload,
98 | },
99 | });
100 |
101 | if (app.isPackaged) {
102 | childWindow.loadFile(indexHtml, { hash: arg });
103 | } else {
104 | childWindow.loadURL(`${url}/#${arg}`);
105 | // childWindow.webContents.openDevTools({ mode: "undocked", activate: true })
106 | }
107 | });
108 |
--------------------------------------------------------------------------------
/apps/web/electron/preload/index.ts:
--------------------------------------------------------------------------------
1 | function domReady(condition: DocumentReadyState[] = ['complete', 'interactive']) {
2 | return new Promise((resolve) => {
3 | if (condition.includes(document.readyState)) {
4 | resolve(true);
5 | } else {
6 | document.addEventListener('readystatechange', () => {
7 | if (condition.includes(document.readyState)) {
8 | resolve(true);
9 | }
10 | });
11 | }
12 | });
13 | }
14 |
15 | const safeDOM = {
16 | append(parent: HTMLElement, child: HTMLElement) {
17 | if (!Array.from(parent.children).find((e) => e === child)) {
18 | return parent.appendChild(child);
19 | }
20 |
21 | return null;
22 | },
23 | remove(parent: HTMLElement, child: HTMLElement) {
24 | if (Array.from(parent.children).find((e) => e === child)) {
25 | return parent.removeChild(child);
26 | }
27 |
28 | return null;
29 | },
30 | };
31 |
32 | /**
33 | * https://tobiasahlin.com/spinkit
34 | * https://connoratherton.com/loaders
35 | * https://projects.lukehaas.me/css-loaders
36 | * https://matejkustec.github.io/SpinThatShit
37 | */
38 | function useLoading() {
39 | const className = `lds-ring`;
40 | const styleContent = `
41 | .app-loading-wrap {
42 | position: fixed;
43 | top: 0;
44 | left: 0;
45 | width: 100vw;
46 | height: 100vh;
47 | display: flex;
48 | align-items: center;
49 | justify-content: center;
50 | background: #111827;
51 | z-index: 9;
52 | }
53 |
54 | .${className} {
55 | display: inline-block;
56 | position: relative;
57 | width: 64px;
58 | height: 64px;
59 | }
60 |
61 | .${className} div {
62 | box-sizing: border-box;
63 | display: block;
64 | position: absolute;
65 | width: 48px;
66 | height: 48px;
67 | margin: 8px;
68 | border: 3px solid #4F46E5;
69 | border-radius: 50%;
70 | animation: ${className} 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
71 | border-color: #4F46E5 transparent transparent transparent;
72 | }
73 |
74 | .${className} div:nth-child(1) {
75 | animation-delay: -0.45s;
76 | }
77 |
78 | .${className} div:nth-child(2) {
79 | animation-delay: -0.3s;
80 | }
81 |
82 | .${className} div:nth-child(3) {
83 | animation-delay: -0.15s;
84 | }
85 |
86 | @keyframes ${className} {
87 | 0% {
88 | transform: rotate(0deg);
89 | }
90 | 100% {
91 | transform: rotate(360deg);
92 | }
93 | }
94 | `;
95 |
96 | const oStyle = document.createElement('style');
97 | oStyle.innerHTML = styleContent;
98 |
99 | const oDiv = document.createElement('div');
100 | oDiv.className = 'app-loading-wrap';
101 | oDiv.innerHTML = ``;
102 |
103 | return {
104 | appendLoading() {
105 | safeDOM.append(document.head, oStyle);
106 | safeDOM.append(document.body, oDiv);
107 | },
108 | removeLoading() {
109 | safeDOM.remove(document.head, oStyle);
110 | safeDOM.remove(document.body, oDiv);
111 | },
112 | };
113 | }
114 |
115 | // ----------------------------------------------------------------------
116 |
117 | const { appendLoading, removeLoading } = useLoading();
118 | domReady().then(appendLoading);
119 |
120 | window.onmessage = (ev) => {
121 | ev.data.payload === 'removeLoading' && removeLoading();
122 | };
123 |
124 | setTimeout(removeLoading, 30000);
125 |
--------------------------------------------------------------------------------
/apps/web/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | nx-nestjs-vue
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/apps/web/project.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "web",
3 | "$schema": "https://raw.githubusercontent.com/nrwl/nx/master/packages/nx/schemas/project-schema.json",
4 | "sourceRoot": "apps/web/src",
5 | "projectType": "application",
6 | "targets": {
7 | "build": {
8 | "executor": "@nx/vite:build",
9 | "outputs": ["{options.outputPath}"],
10 | "options": {
11 | "outputPath": "dist/apps/web",
12 | "configFile": "apps/web/vite.config.ts"
13 | },
14 | "configurations": {
15 | "production": {}
16 | }
17 | },
18 | "serve": {
19 | "executor": "@nx/vite:dev-server",
20 | "options": {
21 | "buildTarget": "web:build",
22 | "open": true
23 | }
24 | },
25 | "electron": {
26 | "executor": "nx:run-commands",
27 | "options": {
28 | "commands": [
29 | "nx serve web"
30 | ],
31 | "parallel": false
32 | },
33 | "configurations": {
34 | "build": {
35 | "commands": [
36 | "nx build web",
37 | "electron-builder"
38 | ]
39 | }
40 | }
41 | },
42 | "lint": {
43 | "executor": "@nx/linter:eslint",
44 | "outputs": ["{options.outputFile}"],
45 | "options": {
46 | "lintFilePatterns": ["apps/web/**/*.{ts,js,tsx,jsx,vue}"],
47 | "fix": true
48 | }
49 | },
50 | "test": {
51 | "executor": "@nx/vite:test",
52 | "outputs": ["coverage/apps/web"],
53 | "options": {
54 | "passWithNoTests": true,
55 | "reportsDirectory": "../../coverage/apps/web"
56 | }
57 | },
58 | "typecheck": {
59 | "executor": "nx:run-commands",
60 | "options": {
61 | "commands": [
62 | {
63 | "command": "vue-tsc --noEmit -p tsconfig.json"
64 | }
65 | ],
66 | "cwd": "apps/web"
67 | }
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/apps/web/src/app/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
49 |
--------------------------------------------------------------------------------
/apps/web/src/app/components/AppAccountMenu.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
{{ accountStore.state.fullname }}
6 |
7 | {{
8 | accountStore.state.position ? `${accountStore.state.position} • ` : ''
9 | }}
10 | {{ accountStore.state.roleName }}
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | {{ $t('account_menu_settings') }}
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | {{ $t('account_menu_logout') }}
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
61 |
62 |
71 |
--------------------------------------------------------------------------------
/apps/web/src/app/components/AppCard.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
{{ props.title }}
21 |
22 |
23 |
24 |
25 |
26 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
59 |
--------------------------------------------------------------------------------
/apps/web/src/app/components/AppDialog.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
{{ props.title }}
25 |
26 |
27 |
32 |
33 |
34 |
39 |
40 |
41 |
42 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
91 |
--------------------------------------------------------------------------------
/apps/web/src/app/components/AppNetworkError.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 | {{ $t('app_connection_lost') }}
10 |
11 |
12 |
13 |
17 |
--------------------------------------------------------------------------------
/apps/web/src/app/components/AppPage.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
29 |
--------------------------------------------------------------------------------
/apps/web/src/app/components/AppPageHeader.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | {{ props.title }}
8 |
9 |
10 |
11 |
12 |
13 |
14 |
22 |
--------------------------------------------------------------------------------
/apps/web/src/app/components/AvatarChip.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | {{ props.title }}
9 |
10 |
11 | {{ props.subtitle }}
12 |
13 |
14 |
15 |
16 |
17 |
18 | {{ props.title }}
19 |
20 |
21 | {{ props.subtitle }}
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
41 |
42 |
52 |
--------------------------------------------------------------------------------
/apps/web/src/app/components/headers/DashboardHeader.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ $t('dashboard_welcome', { name: accountStore.state.firstName }) }}
6 |
7 |
{{ accountStore.state.email }}
8 |
9 |
10 |
11 |
12 |
16 |
--------------------------------------------------------------------------------
/apps/web/src/app/components/headers/UserHeader.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | {{ props.title }}
10 |
11 |
12 |
13 |
14 |
15 |
16 |
24 |
--------------------------------------------------------------------------------
/apps/web/src/app/components/inputs/VInput.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
46 |
--------------------------------------------------------------------------------
/apps/web/src/app/components/inputs/VSelect.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
49 |
--------------------------------------------------------------------------------
/apps/web/src/app/layouts/app.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
14 |
15 |
16 |
17 |
18 |
23 |
24 |
25 |
26 |
27 |
28 |
38 |
--------------------------------------------------------------------------------
/apps/web/src/app/layouts/default.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/apps/web/src/app/pages/dashboard.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/apps/web/src/app/pages/users/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/apps/web/src/assets/main.scss:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700;800;900&display=swap');
2 |
3 | body:not(i) {
4 | font-family: 'Poppins', sans-serif;
5 | }
6 |
7 | .app-header {
8 | box-shadow: 0 12px 12px -12px rgb(0 0 0 / 20%);
9 | }
10 |
11 | .app-drawer {
12 | background-color: #111827;
13 | color: #ffffff;
14 | box-shadow: 0px 0px 12px rgb(0 0 0 / 20%);
15 | }
16 |
17 | .app-page-container {
18 | background-color: #f1f5f9;
19 | min-height: 100px;
20 | }
21 |
22 | .container {
23 | margin: auto;
24 | padding: 38px;
25 | max-width: 1600px;
26 |
27 | @media (max-width: 600px){
28 | padding: 18px;
29 | }
30 | }
31 |
32 | .border-bottom-accent {
33 | border-bottom: 1px solid #e2e8f0;
34 | }
35 |
36 | .q-card {
37 | border-radius: 5px;
38 | box-shadow: 0px 2px 3px -3px rgba(66, 68, 90, 0.72);
39 | display: flex;
40 | flex-direction: column;
41 | border-radius: 25px !important;
42 |
43 | .title-bar {
44 | display: flex;
45 | align-items: center;
46 | min-height: 80px;
47 | padding-left: 30px;
48 | padding-right: 30px;
49 | font-weight: 600;
50 | border-bottom: 1px solid #E6E6E6;
51 |
52 | &:empty {
53 | min-height: 0;
54 | border-bottom: none;
55 | }
56 |
57 | h5 {
58 | margin: 0;
59 | font-weight: 500;
60 | font-size: 20px;
61 | }
62 | }
63 |
64 | >form, >fragment {
65 | height: 100%;
66 | display: flex;
67 | flex-direction: column;
68 | }
69 |
70 | .q-card__section {
71 | padding: 10px 30px;
72 |
73 | &:first-child {
74 | padding-top: 30px;
75 | }
76 |
77 | &:last-child {
78 | padding-bottom: 30px;
79 | }
80 | }
81 |
82 | .q-card__actions {
83 | display: flex;
84 | padding: 0;
85 | padding-left: 30px;
86 | padding-right: 30px;
87 | align-items: center;
88 | min-height: 80px !important;
89 | border-top: 1px solid #E6E6E6;
90 |
91 | .q-btn {
92 | font-size: 14px;
93 | padding: 6px 15px 6px 15px;
94 | }
95 | }
96 | }
97 |
98 | body.platform-android:not(.native-mobile) .q-dialog__inner--minimized > div {
99 | max-height: 100%;
100 | }
101 |
102 | .q-dialog-max-width {
103 | width: 100%;
104 | max-width: 500px;
105 | }
106 |
107 | .q-dialog__inner {
108 | >div {
109 | overflow: hidden;
110 | }
111 | }
112 |
113 | .q-pagination {
114 | .q-btn {
115 | font-size: 14px;
116 | line-height: 13px;
117 | padding: 8px !important;
118 | }
119 | }
120 |
121 | .q-dialog-max-width {
122 | width: 100%;
123 | max-width: 500px;
124 | }
125 |
--------------------------------------------------------------------------------
/apps/web/src/assets/quasar.scss:
--------------------------------------------------------------------------------
1 | $primary : #4F46E5;
2 | $secondary : #111827;
3 | $accent : #9C27B0;
4 |
5 | $dark : #1D1D1D;
6 |
7 | $positive : #21BA45;
8 | $negative : #EF5350;
9 | $info : #31CCEC;
10 | $warning : #F2C037;
11 |
--------------------------------------------------------------------------------
/apps/web/src/common/api/client.ts:
--------------------------------------------------------------------------------
1 | import { config } from '@/config';
2 | import axios from 'axios';
3 |
4 | export const $axios = axios.create({
5 | baseURL: `${config.api.host}:${config.api.port}/api`,
6 | withCredentials: true,
7 | });
8 |
9 | export function setAuthorizationToken(token: string): void {
10 | $axios.defaults.headers.common['Authorization'] = `Bearer ${token}`;
11 | }
12 |
13 | export function clearAuthorizationToken(): void {
14 | $axios.defaults.headers.common['Authorization'] = undefined;
15 | }
16 |
--------------------------------------------------------------------------------
/apps/web/src/common/api/index.ts:
--------------------------------------------------------------------------------
1 | export * from './client';
2 | export * from './interceptors';
3 | export * from './response-error';
4 |
5 | import * as auth from './modules/auth';
6 | import * as users from './modules/users';
7 |
8 | export const api = {
9 | auth,
10 | users,
11 | };
12 |
--------------------------------------------------------------------------------
/apps/web/src/common/api/interceptors.ts:
--------------------------------------------------------------------------------
1 | import { AxiosResponse } from 'axios';
2 | import { timeout } from '@workspace/shared';
3 | import { $axios } from './client';
4 | import { ResponseError } from './response-error';
5 |
6 | type ResponseSuccessCallback = (response: AxiosResponse) => void;
7 | type ResponseErrorCallback = (error: ResponseError) => void;
8 |
9 | interface CallbackTrigger {
10 | responseSuccess: ResponseSuccessCallback;
11 | responseError: ResponseErrorCallback;
12 | }
13 |
14 | const callbackTrigger: CallbackTrigger = {
15 | responseSuccess: null,
16 | responseError: null,
17 | };
18 |
19 | $axios.interceptors.response.use(
20 | (response: AxiosResponse) => {
21 | if (callbackTrigger.responseSuccess) callbackTrigger.responseSuccess(response);
22 | return response;
23 | },
24 |
25 | async (error: ResponseError) => {
26 | if (error.response && error.response.status !== 0) {
27 | error.isNetworkError = false;
28 | if (callbackTrigger.responseError) callbackTrigger.responseError(error);
29 | return Promise.reject(error);
30 | } else {
31 | error.isNetworkError = true;
32 | if (callbackTrigger.responseError) callbackTrigger.responseError(error);
33 | await timeout(5000);
34 | return await $axios.request(error.config);
35 | }
36 | }
37 | );
38 |
39 | export function onResponseSuccess(callback: ResponseSuccessCallback): void {
40 | callbackTrigger.responseSuccess = callback;
41 | }
42 |
43 | export function onResponseError(callback: ResponseErrorCallback): void {
44 | callbackTrigger.responseError = callback;
45 | }
46 |
--------------------------------------------------------------------------------
/apps/web/src/common/api/modules/auth.ts:
--------------------------------------------------------------------------------
1 | import { AuthLoginDto, AuthLoginResponse } from '@workspace/shared';
2 | import { $axios } from '../client';
3 |
4 | export async function login(authLoginDto: AuthLoginDto) {
5 | return await $axios.post('/auth/login', authLoginDto);
6 | }
7 |
8 | export async function logout() {
9 | return await $axios.post('/auth/logout');
10 | }
11 |
--------------------------------------------------------------------------------
/apps/web/src/common/api/modules/users.ts:
--------------------------------------------------------------------------------
1 | import {
2 | PaginationDto,
3 | PaginationResponse,
4 | UserCreateDto,
5 | UserProfileResponse,
6 | UserUpdateDto,
7 | UserUpdateSelfDto,
8 | UserUpdateSelfPasswordDto,
9 | } from '@workspace/shared';
10 |
11 | import { $axios } from '../client';
12 |
13 | export async function getSelf() {
14 | return await $axios.get('/users/self');
15 | }
16 |
17 | export async function updateSelf(data: UserUpdateSelfDto) {
18 | return await $axios.put('/users/self', data);
19 | }
20 |
21 | export async function updateSelfPassword(data: UserUpdateSelfPasswordDto) {
22 | return await $axios.put('/users/self/password', data);
23 | }
24 |
25 | export async function createOne(data: UserCreateDto) {
26 | return await $axios.post('/users/', data);
27 | }
28 |
29 | export async function getMany(data: PaginationDto) {
30 | return await $axios.get>(`/users/`, {
31 | params: data,
32 | });
33 | }
34 |
35 | export async function getOne(id: string) {
36 | return await $axios.get(`/users/${id}`);
37 | }
38 |
39 | export async function updateOne(id: string, data: UserUpdateDto) {
40 | return await $axios.put(`/users/${id}`, data);
41 | }
42 |
43 | export async function deleteOne(id: string) {
44 | return await $axios.delete(`/users/${id}`);
45 | }
46 |
--------------------------------------------------------------------------------
/apps/web/src/common/api/response-error.ts:
--------------------------------------------------------------------------------
1 | import { AxiosError } from 'axios';
2 |
3 | export interface ResponseError extends AxiosError {
4 | isNetworkError: boolean;
5 | }
6 |
--------------------------------------------------------------------------------
/apps/web/src/common/hooks/index.ts:
--------------------------------------------------------------------------------
1 | export * from './use-promise-state';
2 | export * from './use-logout-action';
3 |
--------------------------------------------------------------------------------
/apps/web/src/common/hooks/use-logout-action.ts:
--------------------------------------------------------------------------------
1 | import { useAccountStore } from '@/stores/account';
2 | import { useQuasar, DialogChainObject } from 'quasar';
3 | import { reactive } from 'vue';
4 | import { useI18n } from 'vue-i18n';
5 | import { useRouter } from 'vue-router';
6 | import { api } from '../api';
7 | import { usePromiseState, UsePromiseStateRetrun } from './use-promise-state';
8 |
9 | export interface UseLogoutActionReturn
10 | extends UsePromiseStateRetrun {
11 | logout: () => void;
12 | }
13 |
14 | export function useLogoutAction(): UseLogoutActionReturn {
15 | const router = useRouter();
16 | const $q = useQuasar();
17 | const { t } = useI18n();
18 |
19 | const accountStore = useAccountStore();
20 |
21 | let dialog: DialogChainObject | undefined = undefined;
22 |
23 | function showLogoutDialog(): void {
24 | dialog = $q.dialog({
25 | message: t('account_logout_progress'),
26 | progress: true,
27 | persistent: true,
28 | ok: false,
29 | color: 'primary',
30 | });
31 | }
32 |
33 | function hideLogoutDialog(): void {
34 | if (dialog) dialog.hide();
35 | }
36 |
37 | const logoutAction = usePromiseState(
38 | async () => {
39 | await api.auth.logout();
40 | accountStore.setAuthenticated(false);
41 | hideLogoutDialog();
42 |
43 | $q.notify({
44 | icon: 'mdi-check',
45 | color: 'positive',
46 | message: t('account_logout_success'),
47 | timeout: 1000,
48 | });
49 |
50 | router.push({ name: 'login' });
51 | },
52 | () => {
53 | hideLogoutDialog();
54 |
55 | $q.notify({
56 | icon: 'mdi-close',
57 | color: 'negative',
58 | message: t('account_logout_failed'),
59 | timeout: 1000,
60 | });
61 | }
62 | );
63 |
64 | function logout(): void {
65 | showLogoutDialog();
66 | logoutAction.execute(500);
67 | }
68 |
69 | return reactive({ logout, ...logoutAction });
70 | }
71 |
--------------------------------------------------------------------------------
/apps/web/src/common/hooks/use-promise-state.ts:
--------------------------------------------------------------------------------
1 | import { timeout } from '@workspace/shared';
2 | import { reactive, ref, shallowRef } from 'vue';
3 | import { createEventHook, EventHookOn } from '@vueuse/core';
4 |
5 | export interface UsePromiseStateRetrun {
6 | isReady: boolean;
7 | isLoading: boolean;
8 | counter: number;
9 | startTime: number;
10 | endTime: number;
11 | state: TResult;
12 | error: TError;
13 | onError: EventHookOn;
14 | execute: (delay?: number, payload?: TPayload) => Promise;
15 | }
16 |
17 | export function usePromiseState(
18 | promise: (payload?: any) => Promise,
19 | onError?: (e: TError) => void
20 | ): UsePromiseStateRetrun {
21 | const isReady = ref(false);
22 | const isLoading = ref(false);
23 | const counter = ref(0);
24 | const startTime = ref(undefined);
25 | const endTime = ref(undefined);
26 |
27 | const state = shallowRef(undefined);
28 | const error = shallowRef(undefined);
29 |
30 | const errorEvent = createEventHook();
31 | if (onError) errorEvent.on(onError);
32 |
33 | async function execute(delay?: number, payload?: TPayload): Promise {
34 | isReady.value = false;
35 | isLoading.value = true;
36 | startTime.value = Date.now();
37 | endTime.value = undefined;
38 | error.value = undefined;
39 |
40 | try {
41 | if (delay > 0) await timeout(delay);
42 |
43 | const data = await promise(payload);
44 | state.value = data;
45 | isReady.value = true;
46 | counter.value++;
47 | } catch (e) {
48 | error.value = e as TError;
49 | errorEvent.trigger(error.value);
50 | } finally {
51 | isLoading.value = false;
52 | endTime.value = Date.now();
53 | }
54 |
55 | return state.value;
56 | }
57 |
58 | return reactive({
59 | isReady,
60 | isLoading,
61 | counter,
62 | startTime,
63 | endTime,
64 | state,
65 | error,
66 | onError: errorEvent.on,
67 | execute,
68 | });
69 | }
70 |
--------------------------------------------------------------------------------
/apps/web/src/common/index.ts:
--------------------------------------------------------------------------------
1 | export * from './api';
2 | export * from './hooks';
3 |
--------------------------------------------------------------------------------
/apps/web/src/config.ts:
--------------------------------------------------------------------------------
1 | interface Config {
2 | defaultLocale: string;
3 | useCookies: boolean;
4 | api: {
5 | host: string;
6 | port: number;
7 | };
8 | }
9 |
10 | export const config: Config = {
11 | defaultLocale: import.meta.env.VITE_WEB_DEFAULT_LOCALE ?? 'en',
12 | useCookies: !import.meta.env.VITE_IS_ELECTRON_APP,
13 |
14 | api: {
15 | host: import.meta.env.VITE_WEB_API_URL ?? 'http://localhost',
16 | port: import.meta.env.VITE_WEB_API_PORT ?? 3000,
17 | },
18 | };
19 |
--------------------------------------------------------------------------------
/apps/web/src/locales/en.yml:
--------------------------------------------------------------------------------
1 | app_name: 'nx-nestjs-vue'
2 | app_connection_lost: 'Connection to the server has been lost. Connecting...'
3 |
4 | yes: 'Yes'
5 | no: 'No'
6 | save: 'Save'
7 | add: 'Add'
8 | close: 'Close'
9 | cancel: 'Cancel'
10 | search: 'Search'
11 | edit: 'Edit'
12 | saved_successfully: 'The changes made have been saved.'
13 |
14 | routes_login: 'Sign in'
15 | routes_dashboard: 'Dashboard'
16 | routes_users: 'Users'
17 | routes_user: 'User'
18 | routes_settings: 'Settings'
19 | routes_logout: 'Sign out'
20 |
21 | validation_field_required: 'This field is required!'
22 | validation_string_min: 'This field must contain at least {min} characters!'
23 | validation_string_max: 'This field must contain a maximum of {max} characters!'
24 | validation_string_email: 'This is not a valid e-mail address!'
25 | validation_string_password_repeat: 'Passwords must match!'
26 | validation_string_matches: 'Incorrect format!'
27 |
28 | roles_user: 'User'
29 | roles_admin: 'Administartor'
30 |
31 | menu_main_title: 'Main menu'
32 | menu_main_caption: 'Basic functions of the application'
33 | menu_admin_title: 'Administration'
34 | menu_admin_caption: 'Functions for administrators'
35 | menu_account_title: 'Account'
36 | menu_account_caption: 'User account options'
37 |
38 | account_session_exp: 'Your session has expired!'
39 | account_logout_success: 'Logged out successfully!'
40 | account_logout_failed: 'There was a problem logging out!'
41 | account_logout_progress: 'Signing out ...'
42 | account_menu_settings: 'Settings'
43 | account_menu_logout: 'Sign out'
44 |
45 | login_welcome_title: 'Control panel'
46 | login_welcome_description: 'Lorem ipsum, dolor sit amet consectetur adipisicing elit. Quaerat quas officiis nisi consequatur optio repudiandae corporis. Quos, iste labore? Laboriosam odio enim deserunt sunt illo commodi nostrum in praesentium suscipit autem, ea deleniti consectetur aspernatur fuga minima obcaecati corrupti iusto aliquid voluptatem! Repellat itaque voluptas facilis dolore expedita? Consectetur, facere!'
47 | login_form_email: 'E-mail adress'
48 | login_form_password: 'Password'
49 | login_form_remember: 'Remember me'
50 | login_form_signin: 'Sign in'
51 | login_form_errors_no_authorization: 'Wrong e-mail or password!'
52 |
53 | dashboard_welcome: 'Hello again, {name}!'
54 |
55 | users_title: 'User settings'
56 | users_email: 'E-mail'
57 | users_form_first_name: 'Name'
58 | users_form_last_name: 'Surname'
59 | users_form_position: 'Position'
60 | users_form_role: 'Role'
61 | users_form_isActive: 'Active'
62 | users_form_password: 'Password'
63 | users_form_errors_default: 'Error occured!'
64 | users_form_errors_samelogin: 'The given username already exists in the database!'
65 | users_create_title: 'New user'
66 | users_create_email: 'E-mail'
67 | users_create_first_name: 'Name'
68 | users_create_last_name: 'Surname'
69 | users_create_position: 'Position'
70 | users_create_role: 'Role'
71 | users_create_password: 'Password'
72 | users_create_errors_default: 'Error occured!'
73 | users_create_errors_user_exists: 'A user with such a login already exists!'
74 | users_list_first_and_last_name: 'Name and surname'
75 | users_list_email: 'E-mail'
76 | users_list_role: 'Role'
77 | users_list_created_at: 'Created at'
78 | users_select_search: 'Search for a user'
79 |
80 | settings_general_title: 'General'
81 | settings_general_role: 'Role'
82 | settings_general_email: 'E-mail'
83 | settings_general_form_first_name: 'Name'
84 | settings_general_form_last_name: 'Surname'
85 | settings_general_form_position: 'Position'
86 | settings_general_form_errors_default: 'Error occured!'
87 | settings_password_title: 'Account'
88 | settings_password_form_password: 'Current password'
89 | settings_password_form_new_password: 'New password'
90 | settings_password_form_repeat_password: 'Repeat new password'
91 | settings_password_form_errors_default: 'Error occured!'
92 | settings_password_form_errors_wrong_password: 'Wrong password!'
93 |
--------------------------------------------------------------------------------
/apps/web/src/locales/pl.yml:
--------------------------------------------------------------------------------
1 | app_name: 'nx-nestjs-vue'
2 | app_connection_lost: 'Połączenie z serwerem zostało zerwane. Łączenie...'
3 |
4 | yes: 'Tak'
5 | no: 'Nie'
6 | save: 'Zapisz'
7 | add: 'Dodaj'
8 | close: 'Zamknij'
9 | cancel: 'Anuluj'
10 | search: 'Wyszukaj'
11 | edit: 'Edycja'
12 | saved_successfully: 'Wprowadzone zmiany zostały zapisane.'
13 |
14 | routes_login: 'Logowanie'
15 | routes_dashboard: 'Dashboard'
16 | routes_users: 'Użytkownicy'
17 | routes_user: 'Użytkownik'
18 | routes_settings: 'Ustawienia'
19 | routes_logout: 'Wyloguj'
20 |
21 | validation_field_required: 'To pole jest wymagane!'
22 | validation_string_min: 'To pole musi zawierać minumum {min} znaków!'
23 | validation_string_max: 'To pole musi zawierać maksymalnie {max} znaków!'
24 | validation_string_email: 'To nie jest poprawny adres e-mail!'
25 | validation_string_password_repeat: 'Hasła muszą być identyczne!'
26 | validation_string_matches: 'Niepoprawny format!'
27 |
28 | roles_user: 'Użytkownik'
29 | roles_admin: 'Administartor'
30 |
31 | menu_main_title: 'Menu główne'
32 | menu_main_caption: 'Podstawowe funkcje aplikacji'
33 | menu_admin_title: 'Administracja'
34 | menu_admin_caption: 'Funkcje dla administratorów'
35 | menu_account_title: 'Konto'
36 | menu_account_caption: 'Opcje konta użytkownika'
37 | account_session_exp: 'Sesja wygasła!'
38 | account_logout_success: 'Wylogowano poprawnie!'
39 | account_logout_failed: 'Wystąpił problem podczas wylogowywania!'
40 | account_logout_progress: 'Wylogowywanie...'
41 | account_menu_settings: 'Ustawienia'
42 | account_menu_logout: 'Wyloguj'
43 |
44 | login_welcome_title: 'Panel kontrolny'
45 | login_welcome_description: 'Lorem ipsum, dolor sit amet consectetur adipisicing elit. Quaerat quas officiis nisi consequatur optio repudiandae corporis. Quos, iste labore? Laboriosam odio enim deserunt sunt illo commodi nostrum in praesentium suscipit autem, ea deleniti consectetur aspernatur fuga minima obcaecati corrupti iusto aliquid voluptatem! Repellat itaque voluptas facilis dolore expedita? Consectetur, facere!'
46 | login_form_login: 'Nazwa użytkownika'
47 | login_form_password: 'Hasło'
48 | login_form_remember: 'Zapamiętaj mnie'
49 | login_form_signin: 'Zaloguj się'
50 | login_form_errors_no_authorization: 'Błędny e-mail lub hasło!'
51 |
52 | dashboard_welcome: 'Witaj ponownie, {name}!'
53 |
54 | users_title: 'Ustawienia użytkownika'
55 | users_email: 'E-mail'
56 | users_form_first_name: 'Imię'
57 | users_form_last_name: 'Nazwisko'
58 | users_form_position: 'Stanowisko'
59 | users_form_role: 'Rola'
60 | users_form_isActive: 'Aktywny'
61 | users_form_password: 'Hasło'
62 | users_form_errors_default: 'Wystąpił błąd!'
63 | users_form_errors_samelogin: 'Podana nazwa użytkownika już istnieje w bazie!'
64 | users_create_title: 'Nowy użytkownik'
65 | users_create_email: 'E-mail'
66 | users_create_first_name: 'Imię'
67 | users_create_last_name: 'Nazwisko'
68 | users_create_position: 'Stanowisko'
69 | users_create_role: 'Rola'
70 | users_create_password: 'Hasło'
71 | users_create_errors_default: 'Wystąpił błąd!'
72 | users_create_errors_user_exists: 'Użytkownik o takim loginie już istnieje!'
73 | users_list_first_and_last_name: 'Imię i nazwisko'
74 | users_list_email: 'E-mail'
75 | users_list_role: 'Rola'
76 | users_list_created_at: 'Utworzony'
77 | users_select_search: 'Wyszukaj użytkownika'
78 |
79 | settings_general_title: 'Ogólne'
80 | settings_general_role: 'Rola'
81 | settings_general_email: 'E-mail'
82 | settings_general_form_first_name: 'Imię'
83 | settings_general_form_last_name: 'Nazwisko'
84 | settings_general_form_position: 'Stanowisko'
85 | settings_general_form_errors_default: 'Wystąpił błąd!'
86 | settings_password_title: 'Konto'
87 | settings_password_form_password: 'Aktualne hasło'
88 | settings_password_form_new_password: 'Nowe hasło'
89 | settings_password_form_repeat_password: 'Powtórz nowe hasło'
90 | settings_password_form_errors_default: 'Wystąpił błąd!'
91 | settings_password_form_errors_wrong_password: 'Błędne hasło!'
--------------------------------------------------------------------------------
/apps/web/src/main.ts:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vue';
2 | import { pinia } from './stores';
3 | import router from './router';
4 | import { Quasar, Notify, Dialog } from 'quasar';
5 | import { createI18n } from 'vue-i18n';
6 | import messages from '@intlify/unplugin-vue-i18n/messages';
7 | import { configure as veeConfigure } from 'vee-validate';
8 |
9 | import '@quasar/extras/material-icons/material-icons.css';
10 | import '@quasar/extras/mdi-v6/mdi-v6.css';
11 | import 'quasar/src/css/index.sass';
12 | import './assets/main.scss';
13 |
14 | import App from './app/App.vue';
15 | import { config } from './config';
16 |
17 | veeConfigure({
18 | validateOnInput: true,
19 | });
20 |
21 | const i18n = createI18n({
22 | locale: config.defaultLocale,
23 | legacy: false,
24 | globalInjection: true,
25 | messages,
26 | });
27 |
28 | const app = createApp(App);
29 |
30 | app.use(i18n);
31 | app.use(pinia);
32 | app.use(router);
33 |
34 | app.use(Quasar, {
35 | plugins: {
36 | Notify,
37 | Dialog,
38 | },
39 | });
40 |
41 | app.mount('#app').$nextTick(() => {
42 | postMessage({ payload: 'removeLoading' }, '*');
43 | });
44 |
--------------------------------------------------------------------------------
/apps/web/src/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DhivinX/nx-nestjs-vue/9efaa801ec6b936aeeff49b7a120ad9b92bae51a/apps/web/src/public/favicon.ico
--------------------------------------------------------------------------------
/apps/web/src/router/index.ts:
--------------------------------------------------------------------------------
1 | import { Role } from '@workspace/shared';
2 | import { createRouter, createWebHistory, createWebHashHistory } from 'vue-router';
3 | import { AuthMeta, useAuthGuard, useLoadingIndicator } from './middlewares';
4 | import { routes } from './routes';
5 |
6 | declare module 'vue-router' {
7 | export interface RouteMeta {
8 | auth: AuthMeta;
9 | roles?: Role[];
10 | }
11 | }
12 |
13 | const router = createRouter({
14 | history: import.meta.env.VITE_IS_ELECTRON_APP
15 | ? createWebHashHistory(import.meta.env.BASE_URL)
16 | : createWebHistory(import.meta.env.BASE_URL),
17 | routes,
18 | });
19 |
20 | useLoadingIndicator(router);
21 | useAuthGuard(router);
22 |
23 | export default router;
24 |
--------------------------------------------------------------------------------
/apps/web/src/router/middlewares/index.ts:
--------------------------------------------------------------------------------
1 | export * from './use-auth-guard';
2 | export * from './use-loading-indicator';
3 |
--------------------------------------------------------------------------------
/apps/web/src/router/middlewares/use-auth-guard.ts:
--------------------------------------------------------------------------------
1 | import { Router } from 'vue-router';
2 | import { useAccountStore } from '@/stores/account';
3 |
4 | export enum AuthMeta {
5 | None,
6 | Required,
7 | Optional,
8 | }
9 |
10 | export function useAuthGuard(router: Router) {
11 | router.beforeEach(async (to) => {
12 | const accountStore = useAccountStore();
13 | accountStore.refreshAuthState();
14 |
15 | if (!accountStore.state.loaded && accountStore.state.authenticated)
16 | await accountStore.fetch(500);
17 |
18 | if (to.meta.auth === AuthMeta.Required && !accountStore.state.authenticated) {
19 | return { name: 'login' };
20 | }
21 |
22 | if (to.meta.auth === AuthMeta.None && accountStore.state.authenticated) {
23 | return { name: 'dashboard' };
24 | }
25 |
26 | if (to.meta.roles && !to.meta.roles.includes(accountStore.state.role)) {
27 | return { name: 'dashboard' };
28 | }
29 |
30 | if (to.name === 'user' && to.params.id === accountStore.state.id) {
31 | return { name: 'dashboard' };
32 | }
33 |
34 | return true;
35 | });
36 | }
37 |
--------------------------------------------------------------------------------
/apps/web/src/router/middlewares/use-loading-indicator.ts:
--------------------------------------------------------------------------------
1 | import { Router } from 'vue-router';
2 | import { useAppStore } from '@/stores/app';
3 |
4 | export function useLoadingIndicator(router: Router) {
5 | router.beforeEach(() => {
6 | const appStore = useAppStore();
7 | appStore.state.isRouteLoading = true;
8 | });
9 |
10 | router.afterEach(() => {
11 | const appStore = useAppStore();
12 | appStore.state.isRouteLoading = false;
13 | });
14 | }
15 |
--------------------------------------------------------------------------------
/apps/web/src/router/routes.ts:
--------------------------------------------------------------------------------
1 | import { RouteRecordRaw } from 'vue-router';
2 | import { AuthMeta } from './middlewares';
3 | import { Role } from '@workspace/shared';
4 |
5 | import DefaultLayout from '@/app/layouts/default.vue';
6 | import AppLayout from '@/app/layouts/app.vue';
7 | import LoginPage from '@/app/pages/login.vue';
8 |
9 | export const routes: RouteRecordRaw[] = [
10 | {
11 | path: '/',
12 | component: DefaultLayout,
13 | children: [
14 | {
15 | path: '/',
16 | name: 'login',
17 | component: LoginPage,
18 | meta: {
19 | auth: AuthMeta.None,
20 | },
21 | },
22 | ],
23 | },
24 | {
25 | path: '/app',
26 | component: AppLayout,
27 | children: [
28 | {
29 | path: '/dashboard',
30 | name: 'dashboard',
31 | component: () => import('@/app/pages/dashboard.vue'),
32 | meta: {
33 | auth: AuthMeta.Required,
34 | },
35 | },
36 | {
37 | path: '/users',
38 | component: () => import('@/app/pages/users/index.vue'),
39 | children: [
40 | {
41 | path: 'list',
42 | name: 'users',
43 | component: () => import('@/app/pages/users/list.vue'),
44 | meta: {
45 | auth: AuthMeta.Required,
46 | roles: [Role.Admin],
47 | },
48 | },
49 | {
50 | path: ':id',
51 | name: 'user',
52 | component: () => import('@/app/pages/users/user.vue'),
53 | meta: {
54 | auth: AuthMeta.Required,
55 | roles: [Role.Admin],
56 | },
57 | },
58 | ],
59 | },
60 | {
61 | path: '/settings',
62 | name: 'settings',
63 | component: () => import('@/app/pages/settings.vue'),
64 | meta: {
65 | auth: AuthMeta.Required,
66 | },
67 | },
68 | ],
69 | },
70 | ];
71 |
--------------------------------------------------------------------------------
/apps/web/src/shims-vue.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | declare module '*.vue' {
4 | import { DefineComponent } from 'vue';
5 | // eslint-disable-next-line
6 | const component: DefineComponent<{}, {}, any>;
7 | export default component;
8 | }
9 |
10 | declare module '@intlify/unplugin-vue-i18n/messages' {
11 | import { LocaleMessage } from '@intlify/core-base';
12 | import { VueMessageType } from 'vue-i18n';
13 | const messages: { [x: string]: LocaleMessage };
14 | export default messages;
15 | }
16 |
--------------------------------------------------------------------------------
/apps/web/src/stores/app.ts:
--------------------------------------------------------------------------------
1 | import { reactive } from 'vue';
2 | import { useTitle } from '@vueuse/core';
3 | import { defineStore } from 'pinia';
4 | import { watch } from 'vue';
5 | import { useI18n } from 'vue-i18n';
6 | import { useRoute } from 'vue-router';
7 |
8 | interface State {
9 | networkError: boolean;
10 | isRouteLoading: boolean;
11 | routeTitle: string;
12 | }
13 |
14 | export const useAppStore = defineStore('app', () => {
15 | const route = useRoute();
16 | const { t } = useI18n();
17 | const title = useTitle();
18 |
19 | const state: State = reactive({
20 | networkError: false,
21 | isRouteLoading: false,
22 | routeTitle: t(`app_name`),
23 | });
24 |
25 | watch(
26 | () => route.name,
27 | () => {
28 | title.value = t(`routes_${route.name.toString()}`) + ' - ' + t(`app_name`);
29 | state.routeTitle = t(`routes_${route.name.toString()}`);
30 | }
31 | );
32 |
33 | return { state };
34 | });
35 |
--------------------------------------------------------------------------------
/apps/web/src/stores/index.ts:
--------------------------------------------------------------------------------
1 | import { createPinia } from 'pinia';
2 |
3 | const pinia = createPinia();
4 |
5 | export { pinia };
6 |
--------------------------------------------------------------------------------
/apps/web/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 |
4 | "compilerOptions": {
5 | "outDir": "../../dist/out-tsc",
6 | "types": ["node"],
7 | "composite": true
8 | },
9 |
10 | "include": ["vite.config.*", "vitest.config.*", "cypress.config.*", "../../tsconfig.base.json"]
11 | }
12 |
--------------------------------------------------------------------------------
/apps/web/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "compilerOptions": {
4 | "lib": ["esnext", "dom"],
5 | "target": "esnext",
6 | "jsx": "preserve",
7 | "skipLibCheck": true,
8 | "useDefineForClassFields": true,
9 |
10 | "types": [
11 | "vite/client",
12 | "vitest",
13 | "@intlify/unplugin-vue-i18n/messages"
14 | ],
15 |
16 | "baseUrl": ".",
17 | "paths": {
18 | "@/*": ["./src/*"],
19 | "@workspace/shared": ["../../libs/shared/src/index.ts"]
20 | }
21 | },
22 |
23 | "files": [],
24 |
25 | "include": [
26 | "./src/components.d.ts",
27 | "**/*.js",
28 | "**/*.jsx",
29 | "**/*.ts",
30 | "**/*.tsx",
31 | "**/*.vue",
32 | "**/*.json"
33 | ],
34 |
35 | "exclude": ["**/*.spec.js", "**/*.spec.jsx", "**/*.spec.ts", "**/*.spec.tsx"],
36 |
37 | "references": [
38 | {
39 | "path": "./tsconfig.app.json"
40 | },
41 | {
42 | "path": "./tsconfig.spec.json"
43 | }
44 | ]
45 | }
46 |
--------------------------------------------------------------------------------
/apps/web/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 |
4 | "compilerOptions": {
5 | "outDir": "../../dist/out-tsc",
6 | "types": ["vitest/globals", "node"],
7 | "composite": true
8 | },
9 |
10 | "include": [
11 | "vite.config.ts",
12 | "**/*.test.ts",
13 | "**/*.spec.ts",
14 | "**/*.test.tsx",
15 | "**/*.spec.tsx",
16 | "**/*.test.js",
17 | "**/*.spec.js",
18 | "**/*.test.jsx",
19 | "**/*.spec.jsx",
20 | "**/*.d.ts"
21 | ]
22 | }
23 |
--------------------------------------------------------------------------------
/apps/web/vite.config.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import path from 'path';
4 | import { defineConfig } from 'vite';
5 | import Vue from '@vitejs/plugin-vue';
6 | import Components from 'unplugin-vue-components/vite';
7 | import AutoImport from 'unplugin-auto-import/vite';
8 | import { quasar, transformAssetUrls } from '@quasar/vite-plugin';
9 | import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite';
10 | import checker from 'vite-plugin-checker';
11 | import electron from 'vite-plugin-electron';
12 | import viteTsConfigPaths from 'vite-tsconfig-paths';
13 |
14 | const resolve = (p: string) => path.resolve(__dirname, p);
15 |
16 | export default defineConfig({
17 | server: {
18 | host: true,
19 | port: 8080,
20 |
21 | fs: {
22 | allow: ['../..'],
23 | },
24 | },
25 |
26 | base: process.env.VITE_IS_ELECTRON_APP ? './' : '/',
27 | cacheDir: '../../node_modules/.vite-web',
28 | clearScreen: true,
29 | assetsInclude: /\.(pdf|jpg|png|svg)$/,
30 |
31 | resolve: {
32 | alias: {
33 | '@/': `${resolve('./src')}/`,
34 | },
35 | },
36 |
37 | publicDir: resolve('./src/public'),
38 |
39 | plugins: [
40 | viteTsConfigPaths({
41 | projects: [resolve('../../tsconfig.base.json')],
42 | }),
43 |
44 | Vue({
45 | template: {
46 | transformAssetUrls,
47 | },
48 | }),
49 |
50 | process.env.VITE_DISABLE_VUE_TSC
51 | ? null
52 | : checker({
53 | vueTsc: true,
54 | }),
55 |
56 | VueI18nPlugin({
57 | defaultSFCLang: 'yml',
58 | include: resolve('./src/locales/**'),
59 | }),
60 |
61 | quasar({
62 | sassVariables: resolve('./src/assets/quasar.scss'),
63 | }),
64 |
65 | AutoImport({
66 | dts: resolve('./src/auto-imports.d.ts'),
67 | imports: ['vue', 'vue-router'],
68 | }),
69 |
70 | Components({
71 | dts: resolve('./src/components.d.ts'),
72 | dirs: ['src/app/components'],
73 | }),
74 |
75 | process.env.VITE_IS_ELECTRON_APP
76 | ? electron([
77 | {
78 | entry: path.join(__dirname, 'electron/main/index.ts'),
79 | vite: {
80 | build: {
81 | sourcemap: true,
82 | outDir: 'dist/apps/web/electron/main',
83 | },
84 | },
85 | },
86 |
87 | {
88 | entry: path.join(__dirname, 'electron/preload/index.ts'),
89 | vite: {
90 | build: {
91 | sourcemap: 'inline',
92 | outDir: 'dist/apps/web/electron/preload',
93 | },
94 | },
95 | onstart(options) {
96 | options.reload();
97 | },
98 | },
99 | ])
100 | : null,
101 | ],
102 |
103 | test: {
104 | globals: true,
105 | cache: {
106 | dir: '../../node_modules/.vitest',
107 | },
108 | environment: 'jsdom',
109 | include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
110 | },
111 | });
112 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3.8'
2 | services:
3 | app:
4 | container_name: app
5 | restart: always
6 | build: .
7 | env_file: .env
8 | ports:
9 | - ${NEST_API_HTTP_PORT}:${NEST_API_HTTP_PORT}
10 | depends_on:
11 | - db
12 | environment:
13 | - DATABASE_HOST=db
14 |
15 | db:
16 | container_name: postgres
17 | image: postgres
18 | ports:
19 | - '5433:${DATABASE_PORT}'
20 | volumes:
21 | - data:/data/db
22 | environment:
23 | - POSTGRES_DB=${DATABASE_NAME}
24 | - POSTGRES_USER=${DATABASE_USER}
25 | - POSTGRES_PASSWORD=${DATABASE_PASSWORD}
26 |
27 | volumes:
28 | data: {}
--------------------------------------------------------------------------------
/electron-builder.yml:
--------------------------------------------------------------------------------
1 | ##
2 | # @see https://www.electron.build/configuration/configuration
3 | ##
4 |
5 | appId: "nx-nestjs-vue"
6 | asar: true
7 |
8 | directories:
9 | output: "dist/release/${version}"
10 |
11 | files:
12 | - "dist"
13 |
14 | mac:
15 | artifactName: "${productName}_${version}.${ext}"
16 | target:
17 | - "dmg"
18 |
19 | win:
20 | artifactName: "${productName}_${version}.${ext}"
21 | target:
22 | - target: "nsis"
23 | arch:
24 | - "x64"
25 |
26 | nsis:
27 | oneClick: false
28 | perMachine: false
29 | allowToChangeInstallationDirectory: true
30 | deleteAppDataOnUninstall: false
31 |
--------------------------------------------------------------------------------
/jest.config.ts:
--------------------------------------------------------------------------------
1 | import { getJestProjects } from '@nrwl/jest';
2 |
3 | export default {
4 | projects: getJestProjects(),
5 | };
6 |
--------------------------------------------------------------------------------
/jest.preset.js:
--------------------------------------------------------------------------------
1 | const nxPreset = require('@nrwl/jest/preset').default;
2 |
3 | module.exports = { ...nxPreset };
4 |
--------------------------------------------------------------------------------
/libs/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DhivinX/nx-nestjs-vue/9efaa801ec6b936aeeff49b7a120ad9b92bae51a/libs/.gitkeep
--------------------------------------------------------------------------------
/libs/shared/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [["@nrwl/web/babel", { "useBuiltIns": "usage" }]]
3 | }
4 |
--------------------------------------------------------------------------------
/libs/shared/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["../../.eslintrc.json"]
3 | }
--------------------------------------------------------------------------------
/libs/shared/README.md:
--------------------------------------------------------------------------------
1 | # shared
2 |
3 | This library was generated with [Nx](https://nx.dev).
4 |
5 | ## Running unit tests
6 |
7 | Run `nx test shared` to execute the unit tests via [Jest](https://jestjs.io).
8 |
9 | ## Running lint
10 |
11 | Run `nx lint shared` to execute the lint via [ESLint](https://eslint.org/).
12 |
--------------------------------------------------------------------------------
/libs/shared/jest.config.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | export default {
3 | displayName: 'shared',
4 | preset: '../../jest.preset.js',
5 | globals: {
6 | 'ts-jest': {
7 | tsconfig: '/tsconfig.spec.json',
8 | },
9 | },
10 | transform: {
11 | '^.+\\.[tj]sx?$': 'ts-jest',
12 | },
13 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
14 | coverageDirectory: '../../coverage/libs/shared',
15 | };
16 |
--------------------------------------------------------------------------------
/libs/shared/project.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://raw.githubusercontent.com/nrwl/nx/master/packages/nx/schemas/project-schema.json",
3 | "sourceRoot": "libs/shared/src",
4 | "projectType": "library",
5 | "targets": {
6 | "lint": {
7 | "executor": "@nrwl/linter:eslint",
8 | "outputs": ["{options.outputFile}"],
9 | "options": {
10 | "lintFilePatterns": ["libs/shared/**/*.ts"],
11 | "fix": true
12 | }
13 | },
14 | "test": {
15 | "executor": "@nrwl/jest:jest",
16 | "outputs": ["coverage/libs/shared"],
17 | "options": {
18 | "jestConfig": "libs/shared/jest.config.ts",
19 | "passWithNoTests": true
20 | }
21 | }
22 | },
23 | "tags": []
24 | }
25 |
--------------------------------------------------------------------------------
/libs/shared/src/auth/index.ts:
--------------------------------------------------------------------------------
1 | export * from './inputs/auth-login.input';
2 | export * from './responses/auth-login.response';
3 |
--------------------------------------------------------------------------------
/libs/shared/src/auth/inputs/auth-login.input.ts:
--------------------------------------------------------------------------------
1 | import { bool, object, ObjectSchema, string } from 'yup';
2 | import { setLocale } from 'yup';
3 | import { UseSchema, yupLocale } from '@workspace/shared';
4 |
5 | setLocale(yupLocale);
6 |
7 | export const authLoginSchema: ObjectSchema = object().shape({
8 | email: string().required().email().lowercase().trim(),
9 | password: string().required().min(6),
10 | remember: bool(),
11 | cookies: bool(),
12 | });
13 |
14 | @UseSchema(authLoginSchema)
15 | export class AuthLoginDto {
16 | email: string;
17 | password: string;
18 | remember: boolean;
19 | cookies: boolean;
20 | }
21 |
--------------------------------------------------------------------------------
/libs/shared/src/auth/responses/auth-login.response.ts:
--------------------------------------------------------------------------------
1 | import { UserProfileResponse } from '../../users/responses/user-profile.response';
2 |
3 | export interface AuthLoginResponse {
4 | token?: string;
5 | expirationTime: number;
6 | account: UserProfileResponse;
7 | }
8 |
--------------------------------------------------------------------------------
/libs/shared/src/common/index.ts:
--------------------------------------------------------------------------------
1 | export * from './inputs/pagination.input';
2 | export * from './responses/pagination.response';
3 | export * from './role.enum';
4 |
--------------------------------------------------------------------------------
/libs/shared/src/common/inputs/pagination.input.ts:
--------------------------------------------------------------------------------
1 | import { object, ObjectSchema, setLocale, number, string, bool } from 'yup';
2 | import { UseSchema, yupLocale } from '@workspace/shared';
3 |
4 | setLocale(yupLocale);
5 |
6 | export const paginationDtoSchema: ObjectSchema = object().shape({
7 | page: number().optional().default(1).min(1),
8 | take: number().optional().default(10).min(1).max(50),
9 | sortBy: string().optional(),
10 | descending: bool().optional(),
11 | filter: string().optional(),
12 | });
13 |
14 | @UseSchema(paginationDtoSchema)
15 | export class PaginationDto {
16 | page?: number;
17 | take?: number;
18 | sortBy?: string;
19 | descending?: boolean;
20 | filter?: string;
21 | }
22 |
--------------------------------------------------------------------------------
/libs/shared/src/common/responses/pagination.response.ts:
--------------------------------------------------------------------------------
1 | export interface PaginationResponse {
2 | page: number;
3 | pages: number;
4 | total: number;
5 | elements: T[];
6 | }
7 |
--------------------------------------------------------------------------------
/libs/shared/src/common/role.enum.ts:
--------------------------------------------------------------------------------
1 | export enum Role {
2 | User = 'user',
3 | Admin = 'admin',
4 | }
5 |
6 | export const rolesByImportance: Role[] = [Role.User, Role.Admin];
7 |
--------------------------------------------------------------------------------
/libs/shared/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './utils';
2 | export * from './common';
3 | export * from './auth';
4 | export * from './users';
5 |
--------------------------------------------------------------------------------
/libs/shared/src/users/index.ts:
--------------------------------------------------------------------------------
1 | export * from './inputs/user-create.input';
2 | export * from './inputs/user-update-self-password.input';
3 | export * from './inputs/user-update-self.input';
4 | export * from './inputs/user-update.input';
5 | export * from './responses/user-profile.response';
6 |
--------------------------------------------------------------------------------
/libs/shared/src/users/inputs/user-create.input.ts:
--------------------------------------------------------------------------------
1 | import { mixed, object, ObjectSchema, setLocale, string } from 'yup';
2 | import { UseSchema, yupLocale } from '@workspace/shared';
3 | import { Role } from '../../common';
4 |
5 | setLocale(yupLocale);
6 |
7 | export const userCreateSchema: ObjectSchema = object().shape({
8 | email: string().required().email().lowercase().trim(),
9 | password: string().required().min(6),
10 | firstName: string().required().trim(),
11 | lastName: string().required().trim(),
12 | role: mixed().optional().oneOf(Object.values(Role)),
13 | });
14 |
15 | @UseSchema(userCreateSchema)
16 | export class UserCreateDto {
17 | email: string;
18 | password: string;
19 | firstName: string;
20 | lastName: string;
21 | role?: Role;
22 | }
23 |
--------------------------------------------------------------------------------
/libs/shared/src/users/inputs/user-update-self-password.input.ts:
--------------------------------------------------------------------------------
1 | import { object, ref, ObjectSchema, setLocale, string } from 'yup';
2 | import { UseSchema, yupLocale } from '@workspace/shared';
3 |
4 | setLocale(yupLocale);
5 |
6 | export const userUpdateSelfPasswordSchema: ObjectSchema = object().shape({
7 | password: string().required().min(6),
8 | newPassword: string().required().min(6),
9 | repeatPassword: string()
10 | .required()
11 | .oneOf([ref('newPassword'), null], 'string_password_repeat'),
12 | });
13 |
14 | @UseSchema(userUpdateSelfPasswordSchema)
15 | export class UserUpdateSelfPasswordDto {
16 | password: string;
17 | newPassword: string;
18 | repeatPassword: string;
19 | }
20 |
--------------------------------------------------------------------------------
/libs/shared/src/users/inputs/user-update-self.input.ts:
--------------------------------------------------------------------------------
1 | import { object, ObjectSchema, setLocale, string } from 'yup';
2 | import { UseSchema, yupLocale } from '@workspace/shared';
3 |
4 | setLocale(yupLocale);
5 |
6 | export const userUpdateSelfSchema: ObjectSchema = object().shape({
7 | firstName: string().required().trim(),
8 | lastName: string().required().trim(),
9 | position: string().defined().nullable().trim(),
10 | });
11 |
12 | @UseSchema(userUpdateSelfSchema)
13 | export class UserUpdateSelfDto {
14 | firstName: string;
15 | lastName: string;
16 | position: string | null;
17 | }
18 |
--------------------------------------------------------------------------------
/libs/shared/src/users/inputs/user-update.input.ts:
--------------------------------------------------------------------------------
1 | import { boolean, mixed, setLocale, string, ObjectSchema } from 'yup';
2 | import { UseSchema, yupLocale } from '@workspace/shared';
3 | import { UserUpdateSelfDto, userUpdateSelfSchema } from './user-update-self.input';
4 | import { Role } from '../../common';
5 |
6 | setLocale(yupLocale);
7 |
8 | export const userUpdateSchema: ObjectSchema = userUpdateSelfSchema.shape({
9 | password: string().when({
10 | is: (val: string | null) => val.length && val.length > 0,
11 | then: (schema) => schema.min(6),
12 | }),
13 |
14 | isActive: boolean().required(),
15 | role: mixed().oneOf(Object.values(Role)),
16 | });
17 |
18 | @UseSchema(userUpdateSchema)
19 | export class UserUpdateDto extends UserUpdateSelfDto {
20 | password: string;
21 | isActive: boolean;
22 | role: Role;
23 | }
24 |
--------------------------------------------------------------------------------
/libs/shared/src/users/responses/user-profile.response.ts:
--------------------------------------------------------------------------------
1 | import { Role } from '../../common';
2 |
3 | export interface UserProfileResponse {
4 | id: string;
5 | role: Role;
6 | email: string;
7 | firstName: string;
8 | lastName: string;
9 | position: string;
10 | avatar: string;
11 | isActive: boolean;
12 | createdAt: Date;
13 | }
14 |
--------------------------------------------------------------------------------
/libs/shared/src/utils/compare-roles.ts:
--------------------------------------------------------------------------------
1 | import { Role, rolesByImportance } from '@workspace/shared';
2 |
3 | type ConditionOperator = '<' | '>' | '<=' | '>=' | '=';
4 |
5 | export function rawCompareRoles(role1: Role, role2: Role): number {
6 | if (rolesByImportance.indexOf(role1) > rolesByImportance.indexOf(role2)) return 1;
7 | else if (rolesByImportance.indexOf(role1) === rolesByImportance.indexOf(role2)) return 0;
8 | else return -1;
9 | }
10 |
11 | export function compareRoles(role1: Role, operator: ConditionOperator, role2: Role): boolean {
12 | switch (operator) {
13 | case '>':
14 | return rawCompareRoles(role1, role2) == 1;
15 | case '=':
16 | return rawCompareRoles(role1, role2) == 0;
17 | case '<':
18 | return rawCompareRoles(role1, role2) == -1;
19 | case '>=':
20 | return rawCompareRoles(role1, role2) >= 0;
21 | case '<=':
22 | return rawCompareRoles(role1, role2) <= 0;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/libs/shared/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | export * from './timeout';
2 | export * from './use-schema';
3 | export * from './yup-locale';
4 | export * from './compare-roles';
5 |
--------------------------------------------------------------------------------
/libs/shared/src/utils/timeout.ts:
--------------------------------------------------------------------------------
1 | export function timeout(ms: number): Promise {
2 | return new Promise((resolve) => setTimeout(resolve, ms));
3 | }
4 |
--------------------------------------------------------------------------------
/libs/shared/src/utils/use-schema.ts:
--------------------------------------------------------------------------------
1 | import { ObjectSchema } from 'yup';
2 |
3 | export function UseSchema(schema: ObjectSchema) {
4 | return function (target) {
5 | target.prototype.schema = schema;
6 | };
7 | }
8 |
--------------------------------------------------------------------------------
/libs/shared/src/utils/yup-locale.ts:
--------------------------------------------------------------------------------
1 | import { LocaleObject } from 'yup';
2 |
3 | function toObject(key: string, values?: any) {
4 | delete values?.originalValue;
5 | delete values?.value;
6 |
7 | return {
8 | k: key,
9 | v: values,
10 | };
11 | }
12 |
13 | export const yupLocale: LocaleObject = {
14 | mixed: {
15 | default: (v) => toObject('field_default', v),
16 | required: (v) => toObject('field_required', v),
17 | oneOf: (v) => toObject('field_oneof', v),
18 | notOneOf: (v) => toObject('field_notoneof', v),
19 | notType: (v) => toObject('field_nottype', v),
20 | defined: (v) => toObject('field_defined', v),
21 | },
22 |
23 | string: {
24 | length: (v) => toObject('string_length', v),
25 | min: (v) => toObject('string_min', v),
26 | max: (v) => toObject('string_max', v),
27 | matches: (v) => toObject('string_matches', v),
28 | email: (v) => toObject('string_email', v),
29 | url: (v) => toObject('string_url', v),
30 | uuid: (v) => toObject('string_uuid', v),
31 | trim: (v) => toObject('string_trim', v),
32 | lowercase: (v) => toObject('string_lowercase', v),
33 | uppercase: (v) => toObject('string_uppercase', v),
34 | },
35 |
36 | number: {
37 | min: (v) => toObject('number_min', v),
38 | max: (v) => toObject('number_max', v),
39 | lessThan: (v) => toObject('number_lessthan', v),
40 | moreThan: (v) => toObject('number_morethan', v),
41 | positive: (v) => toObject('number_positive', v),
42 | negative: (v) => toObject('number_negative', v),
43 | integer: (v) => toObject('number_integer', v),
44 | },
45 |
46 | date: {
47 | min: (v) => toObject('date_min', v),
48 | max: (v) => toObject('date_max', v),
49 | },
50 |
51 | boolean: {
52 | isValue: (v) => toObject('boolean_isvalue', v),
53 | },
54 |
55 | object: {
56 | noUnknown: (v) => toObject('object_nounknown', v),
57 | },
58 |
59 | array: {
60 | length: (v) => toObject('array_length', v),
61 | min: (v) => toObject('array_min', v),
62 | max: (v) => toObject('array_max', v),
63 | },
64 | };
65 |
--------------------------------------------------------------------------------
/libs/shared/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "files": [],
4 | "include": [],
5 | "references": [
6 | {
7 | "path": "./tsconfig.lib.json"
8 | },
9 | {
10 | "path": "./tsconfig.spec.json"
11 | }
12 | ],
13 | "compilerOptions": {
14 | "forceConsistentCasingInFileNames": true,
15 | "noFallthroughCasesInSwitch": true
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/libs/shared/tsconfig.lib.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../../dist/out-tsc",
5 | "declaration": true,
6 | "types": []
7 | },
8 | "include": ["**/*.ts"],
9 | "exclude": ["jest.config.ts", "**/*.spec.ts"]
10 | }
11 |
--------------------------------------------------------------------------------
/libs/shared/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../../dist/out-tsc",
5 | "module": "commonjs",
6 | "types": ["jest", "node"]
7 | },
8 | "include": [
9 | "jest.config.ts",
10 | "**/*.test.ts",
11 | "**/*.spec.ts",
12 | "**/*.test.tsx",
13 | "**/*.spec.tsx",
14 | "**/*.test.js",
15 | "**/*.spec.js",
16 | "**/*.test.jsx",
17 | "**/*.spec.jsx",
18 | "**/*.d.ts"
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/nx.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://raw.githubusercontent.com/nrwl/nx/master/packages/nx/schemas/nx-schema.json",
3 | "npmScope": "workspace",
4 | "affected": {
5 | "defaultBase": "master"
6 | },
7 | "implicitDependencies": {
8 | "package.json": {
9 | "dependencies": "*",
10 | "devDependencies": "*"
11 | },
12 | ".eslintrc.json": "*"
13 | },
14 | "tasksRunnerOptions": {
15 | "default": {
16 | "runner": "nx/tasks-runners/default",
17 | "options": {
18 | "cacheableOperations": ["build", "lint", "test", "e2e"]
19 | }
20 | }
21 | },
22 | "targetDependencies": {
23 | "build": [
24 | {
25 | "target": "build",
26 | "projects": "dependencies"
27 | }
28 | ]
29 | },
30 | "defaultProject": "server"
31 | }
32 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nx-nestjs-vue",
3 | "version": "0.0.0",
4 | "license": "MIT",
5 | "private": true,
6 | "main": "dist/apps/web/electron/main/index.js",
7 | "scripts": {
8 | "apps:dev": "nx run-many --target=serve --projects=web,server",
9 | "web:dev": "nx serve web",
10 | "web:electron:dev": "nx electron web",
11 | "web:electron:build": "nx electron web --configuration=build",
12 | "mobile:dev": "nx serve mobile",
13 | "mobile:android": "nx build mobile && nx run mobile:sync:android && nx run mobile:open:android",
14 | "server:dev": "nx serve server",
15 | "server:seed": "node dist/apps/server/cli.js seed",
16 | "build": "npm run clean && nx run-many --target=lint,test,build --configuration=production --projects=web,server",
17 | "test": "nx run-many --target=test --verbose",
18 | "clean": "rimraf dist",
19 | "lint": "nx run-many --target=lint",
20 | "docker": "npm run build && docker-compose up --force-recreate --build -d && docker image prune -f",
21 | "dep-graph": "node tools/scripts/patch-graph.js && nx graph"
22 | },
23 | "devDependencies": {
24 | "@capacitor/android": "^5.0.4",
25 | "@capacitor/cli": "^5.0.4",
26 | "@capacitor/ios": "^5.0.4",
27 | "@intlify/unplugin-vue-i18n": "^0.11.0",
28 | "@nestjs/schematics": "^9.2.0",
29 | "@nestjs/testing": "^9.4.2",
30 | "@nx/js": "16.2.2",
31 | "@nx/eslint-plugin": "16.2.2",
32 | "@nx/jest": "16.2.2",
33 | "@nx/linter": "16.2.2",
34 | "@nx/nest": "16.2.2",
35 | "@nx/node": "16.2.2",
36 | "@nx/vite": "^16.2.2",
37 | "@nx/webpack": "16.2.2",
38 | "@nx/workspace": "16.2.2",
39 | "@nxext/capacitor": "^16.2.0",
40 | "@quasar/vite-plugin": "^1.3.3",
41 | "@types/cron": "^2.0.1",
42 | "@types/express": "^4.17.17",
43 | "@types/jest": "29.5.1",
44 | "@types/node": "^20.2.5",
45 | "@types/passport-jwt": "^3.0.8",
46 | "@types/tough-cookie": "^4.0.2",
47 | "@types/uuid": "^9.0.1",
48 | "@typescript-eslint/eslint-plugin": "~5.59.8",
49 | "@typescript-eslint/parser": "~5.59.8",
50 | "@vitejs/plugin-vue": "^4.2.3",
51 | "@vue/eslint-config-prettier": "^7.1.0",
52 | "@vue/eslint-config-typescript": "^11.0.3",
53 | "electron": "^25.0.0",
54 | "electron-builder": "^23.6.0",
55 | "eslint": "~8.41.0",
56 | "eslint-config-prettier": "^8.8.0",
57 | "eslint-plugin-import": "2.27.5",
58 | "eslint-plugin-prettier": "^4.2.1",
59 | "eslint-plugin-vue": "^9.14.1",
60 | "jest": "29.5.0",
61 | "jest-environment-jsdom": "^29.5.0",
62 | "jsdom": "~22.1.0",
63 | "nx": "16.2.2",
64 | "prettier": "^2.8.8",
65 | "rimraf": "^5.0.1",
66 | "sass": "1.32.12",
67 | "ts-jest": "29.1.0",
68 | "typescript": "^5.0.4",
69 | "unplugin-auto-import": "^0.16.4",
70 | "unplugin-vue-components": "^0.25.0",
71 | "vite": "^4.3.9",
72 | "vite-plugin-checker": "^0.6.0",
73 | "vite-plugin-electron-renderer": "^0.14.5",
74 | "vite-plugin-eslint": "1.8.1",
75 | "vite-tsconfig-paths": "^4.2.0",
76 | "vue-eslint-parser": "^9.3.0",
77 | "vue-tsc": "^1.6.5"
78 | },
79 | "dependencies": {
80 | "@capacitor/core": "^5.0.4",
81 | "@nestjs/common": "^9.4.2",
82 | "@nestjs/config": "^2.3.2",
83 | "@nestjs/core": "^9.4.2",
84 | "@nestjs/passport": "^9.0.3",
85 | "@nestjs/platform-express": "^9.4.2",
86 | "@nestjs/schedule": "^2.2.2",
87 | "@nestjs/serve-static": "^3.0.1",
88 | "@nestjs/typeorm": "^9.0.1",
89 | "@quasar/extras": "1.16.4",
90 | "@vueuse/core": "^10.1.2",
91 | "@vueuse/integrations": "^10.1.2",
92 | "axios": "^1.4.0",
93 | "cookie-parser": "^1.4.6",
94 | "helmet": "^7.0.0",
95 | "jsonwebtoken": "^9.0.0",
96 | "nest-commander": "^3.7.1",
97 | "passport": "^0.6.0",
98 | "passport-jwt": "^4.0.1",
99 | "pg": "^8.11.0",
100 | "pinia": "^2.1.3",
101 | "quasar": "^2.12.0",
102 | "rxjs": "^7.8.1",
103 | "shelljs": "^0.8.5",
104 | "tslib": "^2.5.2",
105 | "typeorm": "^0.3.16",
106 | "universal-cookie": "^4.0.4",
107 | "uuid": "^9.0.0",
108 | "vee-validate": "^4.9.5",
109 | "vite-plugin-electron": "^0.11.2",
110 | "vue": "^3.3.4",
111 | "vue-i18n": "^9.2.2",
112 | "vue-router": "^4.2.2",
113 | "yup": "^1.2.0"
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/tools/generators/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DhivinX/nx-nestjs-vue/9efaa801ec6b936aeeff49b7a120ad9b92bae51a/tools/generators/.gitkeep
--------------------------------------------------------------------------------
/tools/scripts/patch-graph.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin node
2 | const fs = require('fs');
3 | const path = require('path');
4 |
5 | /**
6 | * Patch dep-graph builder function to support Vue files.
7 | * @see https://github.com/nrwl/nx/issues/2960
8 | */
9 | function patchNxDepGraph() {
10 | try {
11 | const filePath = getFilePath();
12 | const fileContent = fs.readFileSync(filePath).toString('utf-8');
13 | const replacement = `extension !== \'.ts\' && extension !== \'.vue\'`;
14 | if (fileContent.includes(replacement)) {
15 | return;
16 | }
17 | fs.writeFileSync(filePath, fileContent.replace(`extension !== '.ts'`, replacement));
18 | console.log('Successfully patched Nx dep-graph for Vue support.');
19 | } catch (err) {
20 | console.error('Failed to patch Nx dep-graph for Vue support.', err);
21 | }
22 | }
23 |
24 | function getFilePath() {
25 | const possiblePaths = [
26 | 'node_modules/nx/src/project-graph/build-dependencies/typescript-import-locator.js',
27 | 'node_modules/@nrwl/workspace/src/core/project-graph/build-dependencies/typescript-import-locator.js',
28 | ];
29 |
30 | for (const p of possiblePaths) {
31 | const fullPath = path.join(process.env.INIT_CWD || '', p);
32 | if (fs.existsSync(fullPath)) {
33 | return fullPath;
34 | }
35 | }
36 |
37 | throw new Error(`Could not find Nx\'s dep-graph builder in node_modules`);
38 | }
39 |
40 | patchNxDepGraph();
41 |
--------------------------------------------------------------------------------
/tools/tsconfig.tools.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.base.json",
3 | "compilerOptions": {
4 | "outDir": "../dist/out-tsc/tools",
5 | "rootDir": ".",
6 | "module": "commonjs",
7 | "target": "es5",
8 | "types": ["node"],
9 | "importHelpers": false
10 | },
11 | "include": ["**/*.ts"]
12 | }
13 |
--------------------------------------------------------------------------------
/tsconfig.base.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "compilerOptions": {
4 | "rootDir": ".",
5 | "sourceMap": true,
6 | "moduleResolution": "node",
7 | "target": "es2015",
8 | "module": "esnext",
9 | "lib": ["es2017", "dom"],
10 | "emitDecoratorMetadata": true,
11 | "experimentalDecorators": true,
12 | "allowJs": true,
13 | "resolveJsonModule": true,
14 | "importHelpers": true,
15 | "esModuleInterop": true,
16 | "skipLibCheck": false,
17 | "skipDefaultLibCheck": false,
18 |
19 | "strict": true,
20 | "strictBindCallApply": false,
21 | "noImplicitReturns": true,
22 | "strictNullChecks": false,
23 | "noImplicitAny": false,
24 |
25 | "baseUrl": ".",
26 | "paths": {
27 | "@workspace/shared": ["libs/shared/src/index.ts"]
28 | }
29 | },
30 | "exclude": ["node_modules", "tmp"]
31 | }
32 |
--------------------------------------------------------------------------------