auth
This commit is contained in:
@@ -5,3 +5,8 @@ MYSQL_DATABASE=local_db
|
|||||||
DATABASE_HOST=localhost
|
DATABASE_HOST=localhost
|
||||||
DATABASE_PORT=3306
|
DATABASE_PORT=3306
|
||||||
MYSQL_ROOT_PASSWORD=kjsdahflöijsdiu
|
MYSQL_ROOT_PASSWORD=kjsdahflöijsdiu
|
||||||
|
|
||||||
|
# SECURITY
|
||||||
|
# SECURITY
|
||||||
|
JWT_SECRET=
|
||||||
|
JWT_EXPIRES_IN=10m
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
{
|
{
|
||||||
"singleQuote": true,
|
"singleQuote": true,
|
||||||
"trailingComma": "all"
|
"trailingComma": "all",
|
||||||
|
"endOfLine": "auto",
|
||||||
|
"tabWidth": 2
|
||||||
}
|
}
|
||||||
201
api/package-lock.json
generated
201
api/package-lock.json
generated
@@ -9,11 +9,16 @@
|
|||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"license": "UNLICENSED",
|
"license": "UNLICENSED",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@nestjs/axios": "^3.0.3",
|
||||||
"@nestjs/common": "^10.0.0",
|
"@nestjs/common": "^10.0.0",
|
||||||
"@nestjs/config": "^3.2.3",
|
"@nestjs/config": "^3.2.3",
|
||||||
"@nestjs/core": "^10.0.0",
|
"@nestjs/core": "^10.0.0",
|
||||||
|
"@nestjs/jwt": "^10.2.0",
|
||||||
"@nestjs/platform-express": "^10.0.0",
|
"@nestjs/platform-express": "^10.0.0",
|
||||||
"@nestjs/typeorm": "^10.0.2",
|
"@nestjs/typeorm": "^10.0.2",
|
||||||
|
"axios": "^1.7.7",
|
||||||
|
"class-transformer": "^0.5.1",
|
||||||
|
"class-validator": "^0.14.1",
|
||||||
"mysql2": "^3.11.2",
|
"mysql2": "^3.11.2",
|
||||||
"reflect-metadata": "^0.2.0",
|
"reflect-metadata": "^0.2.0",
|
||||||
"rxjs": "^7.8.1",
|
"rxjs": "^7.8.1",
|
||||||
@@ -1551,6 +1556,16 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@nestjs/axios": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@nestjs/axios/-/axios-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-h6TCn3yJwD6OKqqqfmtRS5Zo4E46Ip2n+gK1sqwzNBC+qxQ9xpCu+ODVRFur6V3alHSCSBxb3nNtt73VEdluyA==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@nestjs/common": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0",
|
||||||
|
"axios": "^1.3.1",
|
||||||
|
"rxjs": "^6.0.0 || ^7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@nestjs/cli": {
|
"node_modules/@nestjs/cli": {
|
||||||
"version": "10.4.5",
|
"version": "10.4.5",
|
||||||
"resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-10.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-10.4.5.tgz",
|
||||||
@@ -1688,6 +1703,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@nestjs/jwt": {
|
||||||
|
"version": "10.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@nestjs/jwt/-/jwt-10.2.0.tgz",
|
||||||
|
"integrity": "sha512-x8cG90SURkEiLOehNaN2aRlotxT0KZESUliOPKKnjWiyJOcWurkF3w345WOX0P4MgFzUjGoZ1Sy0aZnxeihT0g==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/jsonwebtoken": "9.0.5",
|
||||||
|
"jsonwebtoken": "9.0.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@nestjs/platform-express": {
|
"node_modules/@nestjs/platform-express": {
|
||||||
"version": "10.4.1",
|
"version": "10.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.4.1.tgz",
|
||||||
@@ -2049,6 +2076,14 @@
|
|||||||
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
|
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/jsonwebtoken": {
|
||||||
|
"version": "9.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.5.tgz",
|
||||||
|
"integrity": "sha512-VRLSGzik+Unrup6BsouBeHsf4d1hOEgYWTm/7Nmw1sXoN1+tRly/Gy/po3yeahnP4jfnQWWAhQAqcNfH7ngOkA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/methods": {
|
"node_modules/@types/methods": {
|
||||||
"version": "1.1.4",
|
"version": "1.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz",
|
||||||
@@ -2065,7 +2100,6 @@
|
|||||||
"version": "20.16.5",
|
"version": "20.16.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.5.tgz",
|
||||||
"integrity": "sha512-VwYCweNo3ERajwy0IUlqqcyZ8/A7Zwa9ZP3MnENWcB11AejO+tLy3pu850goUW2FC/IJMdZUfKpX/yxL1gymCA==",
|
"integrity": "sha512-VwYCweNo3ERajwy0IUlqqcyZ8/A7Zwa9ZP3MnENWcB11AejO+tLy3pu850goUW2FC/IJMdZUfKpX/yxL1gymCA==",
|
||||||
"devOptional": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~6.19.2"
|
"undici-types": "~6.19.2"
|
||||||
}
|
}
|
||||||
@@ -2137,6 +2171,11 @@
|
|||||||
"@types/superagent": "^8.1.0"
|
"@types/superagent": "^8.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/validator": {
|
||||||
|
"version": "13.12.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.12.1.tgz",
|
||||||
|
"integrity": "sha512-w0URwf7BQb0rD/EuiG12KP0bailHKHP5YVviJG9zw3ykAokL0TuxU2TUqMB7EwZ59bDHYdeTIvjI5m0S7qHfOA=="
|
||||||
|
},
|
||||||
"node_modules/@types/yargs": {
|
"node_modules/@types/yargs": {
|
||||||
"version": "17.0.33",
|
"version": "17.0.33",
|
||||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz",
|
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz",
|
||||||
@@ -2741,8 +2780,7 @@
|
|||||||
"node_modules/asynckit": {
|
"node_modules/asynckit": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"node_modules/aws-ssl-profiles": {
|
"node_modules/aws-ssl-profiles": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
@@ -2752,6 +2790,16 @@
|
|||||||
"node": ">= 6.0.0"
|
"node": ">= 6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/axios": {
|
||||||
|
"version": "1.7.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz",
|
||||||
|
"integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"follow-redirects": "^1.15.6",
|
||||||
|
"form-data": "^4.0.0",
|
||||||
|
"proxy-from-env": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/babel-jest": {
|
"node_modules/babel-jest": {
|
||||||
"version": "29.7.0",
|
"version": "29.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz",
|
||||||
@@ -3065,6 +3113,11 @@
|
|||||||
"ieee754": "^1.1.13"
|
"ieee754": "^1.1.13"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/buffer-equal-constant-time": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="
|
||||||
|
},
|
||||||
"node_modules/buffer-from": {
|
"node_modules/buffer-from": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||||
@@ -3229,6 +3282,21 @@
|
|||||||
"integrity": "sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA==",
|
"integrity": "sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/class-transformer": {
|
||||||
|
"version": "0.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz",
|
||||||
|
"integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw=="
|
||||||
|
},
|
||||||
|
"node_modules/class-validator": {
|
||||||
|
"version": "0.14.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.1.tgz",
|
||||||
|
"integrity": "sha512-2VEG9JICxIqTpoK1eMzZqaV+u/EiwEJkMGzTrZf6sU/fwsnOITVgYJ8yojSy6CaXtO9V0Cc6ZQZ8h8m4UBuLwQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/validator": "^13.11.8",
|
||||||
|
"libphonenumber-js": "^1.10.53",
|
||||||
|
"validator": "^13.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cli-cursor": {
|
"node_modules/cli-cursor": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
|
||||||
@@ -3422,7 +3490,6 @@
|
|||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"delayed-stream": "~1.0.0"
|
"delayed-stream": "~1.0.0"
|
||||||
},
|
},
|
||||||
@@ -3698,7 +3765,6 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.4.0"
|
"node": ">=0.4.0"
|
||||||
}
|
}
|
||||||
@@ -3813,6 +3879,14 @@
|
|||||||
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
||||||
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="
|
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="
|
||||||
},
|
},
|
||||||
|
"node_modules/ecdsa-sig-formatter": {
|
||||||
|
"version": "1.0.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
|
||||||
|
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"safe-buffer": "^5.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ee-first": {
|
"node_modules/ee-first": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||||
@@ -4531,6 +4605,25 @@
|
|||||||
"integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==",
|
"integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/follow-redirects": {
|
||||||
|
"version": "1.15.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
|
||||||
|
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"debug": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/foreground-child": {
|
"node_modules/foreground-child": {
|
||||||
"version": "3.3.0",
|
"version": "3.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz",
|
||||||
@@ -4600,7 +4693,6 @@
|
|||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"asynckit": "^0.4.0",
|
"asynckit": "^0.4.0",
|
||||||
"combined-stream": "^1.0.8",
|
"combined-stream": "^1.0.8",
|
||||||
@@ -6136,6 +6228,46 @@
|
|||||||
"graceful-fs": "^4.1.6"
|
"graceful-fs": "^4.1.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/jsonwebtoken": {
|
||||||
|
"version": "9.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
|
||||||
|
"integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"jws": "^3.2.2",
|
||||||
|
"lodash.includes": "^4.3.0",
|
||||||
|
"lodash.isboolean": "^3.0.3",
|
||||||
|
"lodash.isinteger": "^4.0.4",
|
||||||
|
"lodash.isnumber": "^3.0.3",
|
||||||
|
"lodash.isplainobject": "^4.0.6",
|
||||||
|
"lodash.isstring": "^4.0.1",
|
||||||
|
"lodash.once": "^4.0.0",
|
||||||
|
"ms": "^2.1.1",
|
||||||
|
"semver": "^7.5.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12",
|
||||||
|
"npm": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/jwa": {
|
||||||
|
"version": "1.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
|
||||||
|
"integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
|
||||||
|
"dependencies": {
|
||||||
|
"buffer-equal-constant-time": "1.0.1",
|
||||||
|
"ecdsa-sig-formatter": "1.0.11",
|
||||||
|
"safe-buffer": "^5.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/jws": {
|
||||||
|
"version": "3.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
|
||||||
|
"integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
|
||||||
|
"dependencies": {
|
||||||
|
"jwa": "^1.4.1",
|
||||||
|
"safe-buffer": "^5.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/keyv": {
|
"node_modules/keyv": {
|
||||||
"version": "4.5.4",
|
"version": "4.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
||||||
@@ -6176,6 +6308,11 @@
|
|||||||
"node": ">= 0.8.0"
|
"node": ">= 0.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/libphonenumber-js": {
|
||||||
|
"version": "1.11.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.11.8.tgz",
|
||||||
|
"integrity": "sha512-0fv/YKpJBAgXKy0kaS3fnqoUVN8901vUYAKIGD/MWZaDfhJt1nZjPL3ZzdZBt/G8G8Hw2J1xOIrXWdNHFHPAvg=="
|
||||||
|
},
|
||||||
"node_modules/lines-and-columns": {
|
"node_modules/lines-and-columns": {
|
||||||
"version": "1.2.4",
|
"version": "1.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
||||||
@@ -6211,6 +6348,36 @@
|
|||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash.includes": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="
|
||||||
|
},
|
||||||
|
"node_modules/lodash.isboolean": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="
|
||||||
|
},
|
||||||
|
"node_modules/lodash.isinteger": {
|
||||||
|
"version": "4.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
|
||||||
|
"integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="
|
||||||
|
},
|
||||||
|
"node_modules/lodash.isnumber": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="
|
||||||
|
},
|
||||||
|
"node_modules/lodash.isplainobject": {
|
||||||
|
"version": "4.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
|
||||||
|
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="
|
||||||
|
},
|
||||||
|
"node_modules/lodash.isstring": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="
|
||||||
|
},
|
||||||
"node_modules/lodash.memoize": {
|
"node_modules/lodash.memoize": {
|
||||||
"version": "4.1.2",
|
"version": "4.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
|
||||||
@@ -6223,6 +6390,11 @@
|
|||||||
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
|
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash.once": {
|
||||||
|
"version": "4.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
|
||||||
|
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="
|
||||||
|
},
|
||||||
"node_modules/log-symbols": {
|
"node_modules/log-symbols": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
|
||||||
@@ -7098,6 +7270,11 @@
|
|||||||
"node": ">= 0.10"
|
"node": ">= 0.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/proxy-from-env": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
|
||||||
|
},
|
||||||
"node_modules/punycode": {
|
"node_modules/punycode": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||||
@@ -7529,7 +7706,6 @@
|
|||||||
"version": "7.6.3",
|
"version": "7.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
|
||||||
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
|
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
|
||||||
"dev": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"semver": "bin/semver.js"
|
"semver": "bin/semver.js"
|
||||||
},
|
},
|
||||||
@@ -8605,8 +8781,7 @@
|
|||||||
"node_modules/undici-types": {
|
"node_modules/undici-types": {
|
||||||
"version": "6.19.8",
|
"version": "6.19.8",
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
|
||||||
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
|
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="
|
||||||
"devOptional": true
|
|
||||||
},
|
},
|
||||||
"node_modules/universalify": {
|
"node_modules/universalify": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
@@ -8709,6 +8884,14 @@
|
|||||||
"node": ">=10.12.0"
|
"node": ">=10.12.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/validator": {
|
||||||
|
"version": "13.12.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz",
|
||||||
|
"integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vary": {
|
"node_modules/vary": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||||
|
|||||||
@@ -20,11 +20,16 @@
|
|||||||
"test:e2e": "jest --config ./test/jest-e2e.json"
|
"test:e2e": "jest --config ./test/jest-e2e.json"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@nestjs/axios": "^3.0.3",
|
||||||
"@nestjs/common": "^10.0.0",
|
"@nestjs/common": "^10.0.0",
|
||||||
"@nestjs/config": "^3.2.3",
|
"@nestjs/config": "^3.2.3",
|
||||||
"@nestjs/core": "^10.0.0",
|
"@nestjs/core": "^10.0.0",
|
||||||
|
"@nestjs/jwt": "^10.2.0",
|
||||||
"@nestjs/platform-express": "^10.0.0",
|
"@nestjs/platform-express": "^10.0.0",
|
||||||
"@nestjs/typeorm": "^10.0.2",
|
"@nestjs/typeorm": "^10.0.2",
|
||||||
|
"axios": "^1.7.7",
|
||||||
|
"class-transformer": "^0.5.1",
|
||||||
|
"class-validator": "^0.14.1",
|
||||||
"mysql2": "^3.11.2",
|
"mysql2": "^3.11.2",
|
||||||
"reflect-metadata": "^0.2.0",
|
"reflect-metadata": "^0.2.0",
|
||||||
"rxjs": "^7.8.1",
|
"rxjs": "^7.8.1",
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
|
||||||
import { AppController } from './app.controller';
|
|
||||||
import { AppService } from './app.service';
|
|
||||||
|
|
||||||
describe('AppController', () => {
|
|
||||||
let appController: AppController;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
const app: TestingModule = await Test.createTestingModule({
|
|
||||||
controllers: [AppController],
|
|
||||||
providers: [AppService],
|
|
||||||
}).compile();
|
|
||||||
|
|
||||||
appController = app.get<AppController>(AppController);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('root', () => {
|
|
||||||
it('should return "Hello World!"', () => {
|
|
||||||
expect(appController.getHello()).toBe('Hello World!');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Controller, Get } from '@nestjs/common';
|
import { Body, Controller, Get, Post } from '@nestjs/common';
|
||||||
import { AppService } from './app.service';
|
import { AppService } from './app.service';
|
||||||
|
import { LoginDTO } from './model/dto/login.dto';
|
||||||
|
|
||||||
@Controller()
|
@Controller()
|
||||||
export class AppController {
|
export class AppController {
|
||||||
@@ -9,4 +10,9 @@ export class AppController {
|
|||||||
getHello(): any {
|
getHello(): any {
|
||||||
return this.appService.getHello();
|
return this.appService.getHello();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
login(@Body() createUserDto: LoginDTO) {
|
||||||
|
return { success: createUserDto };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { AppController } from './app.controller';
|
import { AppController } from './app.controller';
|
||||||
import { AppService } from './app.service';
|
import { AppService } from './app.service';
|
||||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
import { ConfigModule } from '@nestjs/config';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { DatabaseModule } from './shared/database/database.module';
|
||||||
|
import { AuthModule } from './modules/auth/auth.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -10,28 +11,10 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
|||||||
envFilePath: ['.env'],
|
envFilePath: ['.env'],
|
||||||
isGlobal: true,
|
isGlobal: true,
|
||||||
}),
|
}),
|
||||||
TypeOrmModule.forRootAsync({
|
DatabaseModule,
|
||||||
useFactory: () => ({
|
AuthModule,
|
||||||
type: 'mysql',
|
|
||||||
host: process.env.DATABASE_HOST,
|
|
||||||
port: parseInt(process.env.DATABASE_PORT) || 3306,
|
|
||||||
username: process.env.MYSQL_USER,
|
|
||||||
password: process.env.MYSQL_PASSWORD,
|
|
||||||
database: process.env.MYSQL_DATABASE,
|
|
||||||
synchronize: true,
|
|
||||||
autoLoadEntities: true,
|
|
||||||
retryAttempts: 5,
|
|
||||||
retryDelay: 10000,
|
|
||||||
logging: ['error'],
|
|
||||||
logger: 'file',
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
],
|
],
|
||||||
controllers: [AppController],
|
controllers: [AppController],
|
||||||
providers: [AppService],
|
providers: [AppService],
|
||||||
})
|
})
|
||||||
export class AppModule {
|
export class AppModule {}
|
||||||
constructor(private config: ConfigService) {
|
|
||||||
console.log(this.config.get('MYSQL_USER'))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common';
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AppService {
|
export class AppService {
|
||||||
|
constructor() {}
|
||||||
getHello(): any {
|
getHello(): any {
|
||||||
return { success: true, date: new Date() };
|
return { success: true, date: new Date() };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import { NestFactory } from '@nestjs/core';
|
import { NestFactory, Reflector } from '@nestjs/core';
|
||||||
import { AppModule } from './app.module';
|
import { AppModule } from './app.module';
|
||||||
|
import { ClassSerializerInterceptor, ValidationPipe } from '@nestjs/common';
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const app = await NestFactory.create(AppModule);
|
const app = await NestFactory.create(AppModule);
|
||||||
|
app.useGlobalInterceptors(new ClassSerializerInterceptor(app.get(Reflector)));
|
||||||
|
app.useGlobalPipes(new ValidationPipe());
|
||||||
await app.listen(4000);
|
await app.listen(4000);
|
||||||
}
|
}
|
||||||
bootstrap();
|
bootstrap();
|
||||||
|
|||||||
6
api/src/model/dto/auth-code.dto.ts
Normal file
6
api/src/model/dto/auth-code.dto.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { IsNotEmpty } from 'class-validator';
|
||||||
|
|
||||||
|
export class AuthCodeDto {
|
||||||
|
@IsNotEmpty()
|
||||||
|
code: string;
|
||||||
|
}
|
||||||
9
api/src/model/dto/create-user.dto.ts
Normal file
9
api/src/model/dto/create-user.dto.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { IsEmail, IsNotEmpty } from 'class-validator';
|
||||||
|
|
||||||
|
export class CreateUserDto {
|
||||||
|
@IsEmail()
|
||||||
|
username: string;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
externalId: string;
|
||||||
|
}
|
||||||
2
api/src/model/dto/index.ts
Normal file
2
api/src/model/dto/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './login.dto';
|
||||||
|
export * from './auth-code.dto';
|
||||||
9
api/src/model/dto/login.dto.ts
Normal file
9
api/src/model/dto/login.dto.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { IsEmail, IsNotEmpty } from 'class-validator';
|
||||||
|
|
||||||
|
export class LoginDTO {
|
||||||
|
@IsEmail()
|
||||||
|
username: string;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
2
api/src/model/entitites/index.ts
Normal file
2
api/src/model/entitites/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './sso.user.entity';
|
||||||
|
export * from './user.entity';
|
||||||
18
api/src/model/entitites/sso.user.entity.ts
Normal file
18
api/src/model/entitites/sso.user.entity.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { Column, Entity, JoinColumn, OneToOne, PrimaryColumn } from 'typeorm';
|
||||||
|
import { User } from './user.entity';
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
export class SSOUser {
|
||||||
|
@PrimaryColumn({ type: 'uuid', unique: true })
|
||||||
|
externalId: string;
|
||||||
|
|
||||||
|
@OneToOne(() => User, (user) => user.external)
|
||||||
|
@JoinColumn()
|
||||||
|
user: User;
|
||||||
|
|
||||||
|
@Column({ nullable: true, type: 'text' })
|
||||||
|
accessToken: string;
|
||||||
|
|
||||||
|
@Column({ nullable: true, type: 'text' })
|
||||||
|
refreshToken: string;
|
||||||
|
}
|
||||||
44
api/src/model/entitites/user.entity.ts
Normal file
44
api/src/model/entitites/user.entity.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import { Exclude } from 'class-transformer';
|
||||||
|
import {
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
Entity,
|
||||||
|
OneToOne,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
} from 'typeorm';
|
||||||
|
import { IUser } from '../interface';
|
||||||
|
import { SSOUser } from './sso.user.entity';
|
||||||
|
import { IsEmail } from 'class-validator';
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
export class User implements IUser {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@IsEmail()
|
||||||
|
@Column({ unique: true })
|
||||||
|
username: string;
|
||||||
|
|
||||||
|
@Column({ name: 'first_name', default: '' })
|
||||||
|
firstName: string;
|
||||||
|
|
||||||
|
@Column({ name: 'last_name', default: '' })
|
||||||
|
lastName: string;
|
||||||
|
|
||||||
|
@CreateDateColumn({ name: 'created_at' })
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@Column({ default: null })
|
||||||
|
lastLogin: Date;
|
||||||
|
|
||||||
|
@Exclude()
|
||||||
|
@OneToOne(() => SSOUser, (sso) => sso.user, { eager: true, cascade: true })
|
||||||
|
external: SSOUser;
|
||||||
|
|
||||||
|
@Exclude()
|
||||||
|
@Column({ default: true })
|
||||||
|
isActive: boolean;
|
||||||
|
|
||||||
|
accessToken?: string;
|
||||||
|
refreshToken?: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
export interface IExternalAccessPayload {
|
||||||
|
username: string;
|
||||||
|
id: string;
|
||||||
|
firstName: string;
|
||||||
|
lastName: string;
|
||||||
|
iss: string;
|
||||||
|
aud: string;
|
||||||
|
iat: number;
|
||||||
|
exp: number;
|
||||||
|
}
|
||||||
3
api/src/model/interface/index.ts
Normal file
3
api/src/model/interface/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export * from './user.interface';
|
||||||
|
export * from './external-access-token.payload.interface';
|
||||||
|
export * from './payload.interface';
|
||||||
5
api/src/model/interface/payload.interface.ts
Normal file
5
api/src/model/interface/payload.interface.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export interface IPayload {
|
||||||
|
id: string;
|
||||||
|
username: string;
|
||||||
|
type: 'access' | 'refresh';
|
||||||
|
}
|
||||||
10
api/src/model/interface/user.interface.ts
Normal file
10
api/src/model/interface/user.interface.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
export interface IUser {
|
||||||
|
id: string;
|
||||||
|
username: string;
|
||||||
|
|
||||||
|
firstName: string;
|
||||||
|
lastName: string;
|
||||||
|
|
||||||
|
accessToken?: string;
|
||||||
|
refreshToken?: string;
|
||||||
|
}
|
||||||
2
api/src/model/repositories/index.ts
Normal file
2
api/src/model/repositories/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './user.repository';
|
||||||
|
export * from './ssouser.repository';
|
||||||
18
api/src/model/repositories/ssouser.repository.ts
Normal file
18
api/src/model/repositories/ssouser.repository.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { Repository, DataSource } from 'typeorm';
|
||||||
|
import { SSOUser } from '../entitites';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class SsoUserRepository extends Repository<SSOUser> {
|
||||||
|
constructor(dataSource: DataSource) {
|
||||||
|
super(SSOUser, dataSource.createEntityManager());
|
||||||
|
}
|
||||||
|
|
||||||
|
findOneByUserId(id: string): Promise<SSOUser> {
|
||||||
|
return this.findOne({ where: { user: { id: id } } });
|
||||||
|
}
|
||||||
|
|
||||||
|
findByExternalId(id: string): Promise<SSOUser> {
|
||||||
|
return this.findOne({ where: { externalId: id } });
|
||||||
|
}
|
||||||
|
}
|
||||||
61
api/src/model/repositories/user.repository.ts
Normal file
61
api/src/model/repositories/user.repository.ts
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
||||||
|
import { Repository, DataSource } from 'typeorm';
|
||||||
|
import { User } from '../entitites';
|
||||||
|
import { CreateUserDto } from '../dto/create-user.dto';
|
||||||
|
import { SsoUserRepository } from './ssouser.repository';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class UserRepository extends Repository<User> {
|
||||||
|
constructor(
|
||||||
|
dataSource: DataSource,
|
||||||
|
private ssoRepo: SsoUserRepository,
|
||||||
|
) {
|
||||||
|
super(User, dataSource.createEntityManager());
|
||||||
|
}
|
||||||
|
|
||||||
|
findByUsername(username: string): Promise<User> {
|
||||||
|
return this.findOne({ where: { username }, relations: ['external'] });
|
||||||
|
}
|
||||||
|
|
||||||
|
findById(id: string): Promise<User> {
|
||||||
|
return this.findOne({ where: { id }, relations: ['external'] });
|
||||||
|
}
|
||||||
|
|
||||||
|
async createUser(createUserDto: CreateUserDto): Promise<User> {
|
||||||
|
if (
|
||||||
|
!(await this.checkIfCanInserted(
|
||||||
|
createUserDto.username,
|
||||||
|
createUserDto.externalId,
|
||||||
|
))
|
||||||
|
) {
|
||||||
|
throw new HttpException('user_exists', HttpStatus.UNPROCESSABLE_ENTITY);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const created = this.create(createUserDto);
|
||||||
|
const sso = this.ssoRepo.create({
|
||||||
|
user: created,
|
||||||
|
externalId: createUserDto.externalId,
|
||||||
|
});
|
||||||
|
created.external = sso;
|
||||||
|
const user = await this.save(created);
|
||||||
|
sso.user = user;
|
||||||
|
this.ssoRepo.save(sso);
|
||||||
|
return user;
|
||||||
|
} catch {
|
||||||
|
throw new HttpException(
|
||||||
|
'not_successfull',
|
||||||
|
HttpStatus.UNPROCESSABLE_ENTITY,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async checkIfCanInserted(
|
||||||
|
username: string,
|
||||||
|
externalId: string,
|
||||||
|
): Promise<boolean> {
|
||||||
|
const user = await this.findOneBy({ username });
|
||||||
|
const sso = await this.ssoRepo.findByExternalId(externalId);
|
||||||
|
return user == null && sso == null;
|
||||||
|
}
|
||||||
|
}
|
||||||
26
api/src/modules/auth/auth.controller.ts
Normal file
26
api/src/modules/auth/auth.controller.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Controller,
|
||||||
|
HttpException,
|
||||||
|
HttpStatus,
|
||||||
|
Post,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { AuthService } from './auth.service';
|
||||||
|
import { AuthCodeDto } from 'src/model/dto';
|
||||||
|
import { User } from 'src/model/entitites';
|
||||||
|
|
||||||
|
@Controller('auth')
|
||||||
|
export class AuthController {
|
||||||
|
constructor(private authService: AuthService) {}
|
||||||
|
|
||||||
|
@Post('auth-code')
|
||||||
|
async registerOrLoginWithAuthCode(
|
||||||
|
@Body() authDto: AuthCodeDto,
|
||||||
|
): Promise<User> {
|
||||||
|
const user = await this.authService.registerOrLoginWithAuthCode(authDto);
|
||||||
|
if (user == null) {
|
||||||
|
throw new HttpException('forbidden', HttpStatus.FORBIDDEN);
|
||||||
|
}
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
}
|
||||||
26
api/src/modules/auth/auth.module.ts
Normal file
26
api/src/modules/auth/auth.module.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { AuthController } from './auth.controller';
|
||||||
|
import { AuthService } from './auth.service';
|
||||||
|
import { DatabaseModule } from 'src/shared/database/database.module';
|
||||||
|
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||||
|
import { HttpModule } from '@nestjs/axios';
|
||||||
|
import { JwtModule } from '@nestjs/jwt';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
controllers: [AuthController],
|
||||||
|
providers: [AuthService],
|
||||||
|
imports: [
|
||||||
|
DatabaseModule,
|
||||||
|
ConfigModule,
|
||||||
|
HttpModule,
|
||||||
|
JwtModule.registerAsync({
|
||||||
|
imports: [ConfigModule],
|
||||||
|
inject: [ConfigService],
|
||||||
|
useFactory: async (config: ConfigService) => ({
|
||||||
|
secret: config.get('JWT_SECRET'),
|
||||||
|
signOptions: { expiresIn: config.get('JWT_EXPIRES_IN') },
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class AuthModule {}
|
||||||
106
api/src/modules/auth/auth.service.ts
Normal file
106
api/src/modules/auth/auth.service.ts
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { AuthCodeDto } from 'src/model/dto';
|
||||||
|
import { CreateUserDto } from 'src/model/dto/create-user.dto';
|
||||||
|
import { UserRepository } from 'src/model/repositories';
|
||||||
|
import { HttpService } from '@nestjs/axios';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
import { JwtService } from '@nestjs/jwt';
|
||||||
|
import { IExternalAccessPayload, IPayload } from 'src/model/interface';
|
||||||
|
import { User } from 'src/model/entitites';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AuthService {
|
||||||
|
constructor(
|
||||||
|
private userRepo: UserRepository,
|
||||||
|
private readonly http: HttpService,
|
||||||
|
private configService: ConfigService,
|
||||||
|
private jwt: JwtService,
|
||||||
|
) {}
|
||||||
|
register(register: CreateUserDto) {
|
||||||
|
return this.userRepo.createUser(register);
|
||||||
|
}
|
||||||
|
|
||||||
|
async registerOrLoginWithAuthCode(auth: AuthCodeDto): Promise<User> {
|
||||||
|
console.log(auth);
|
||||||
|
const body = this.createAuthCodeFormData(auth.code, 'authorization_code');
|
||||||
|
const url = this.configService.get('SSO_TOKEN_URL');
|
||||||
|
return new Promise<User>((resolve) => {
|
||||||
|
this.http.post(url, body).subscribe({
|
||||||
|
next: async (response) => {
|
||||||
|
const user = await this.saveExternalTokens(response.data as any);
|
||||||
|
this.generateTokens(user);
|
||||||
|
resolve(user);
|
||||||
|
},
|
||||||
|
error: () => {
|
||||||
|
resolve(null);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async saveExternalTokens({
|
||||||
|
access_token,
|
||||||
|
refresh_token,
|
||||||
|
}: {
|
||||||
|
access_token: string;
|
||||||
|
refresh_token: string;
|
||||||
|
}): Promise<User> {
|
||||||
|
console.log(access_token, refresh_token);
|
||||||
|
const payload: IExternalAccessPayload = this.jwt.decode(access_token);
|
||||||
|
return new Promise<User>(async (resolve) => {
|
||||||
|
let user = await this.userRepo.findByUsername(payload.username);
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
user = await this.userRepo.createUser({
|
||||||
|
username: payload.username,
|
||||||
|
externalId: payload.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
user.firstName = payload.firstName;
|
||||||
|
user.lastName = payload.lastName;
|
||||||
|
|
||||||
|
user.external.accessToken = access_token;
|
||||||
|
user.external.refreshToken = refresh_token;
|
||||||
|
await this.userRepo.save(user);
|
||||||
|
resolve(user);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private generateTokens(user: User) {
|
||||||
|
const payload: IPayload = {
|
||||||
|
username: user.username,
|
||||||
|
id: user.id,
|
||||||
|
type: 'access',
|
||||||
|
};
|
||||||
|
const token = this.jwt.sign(payload);
|
||||||
|
user.accessToken = token;
|
||||||
|
|
||||||
|
const rPay: IPayload = {
|
||||||
|
username: user.username,
|
||||||
|
id: user.id,
|
||||||
|
type: 'refresh',
|
||||||
|
};
|
||||||
|
|
||||||
|
user.refreshToken = this.jwt.sign(rPay);
|
||||||
|
}
|
||||||
|
|
||||||
|
private createAuthCodeFormData(
|
||||||
|
code: string,
|
||||||
|
grant_type = 'authorization_code',
|
||||||
|
): FormData {
|
||||||
|
const bodyFormData = new FormData();
|
||||||
|
bodyFormData.append('client_id', this.configService.get('SSO_CLIENT_ID'));
|
||||||
|
bodyFormData.append(
|
||||||
|
'client_secret',
|
||||||
|
this.configService.get('SSO_CLIENT_SECRET'),
|
||||||
|
);
|
||||||
|
bodyFormData.append('code', code);
|
||||||
|
bodyFormData.append('grant_type', grant_type);
|
||||||
|
bodyFormData.append(
|
||||||
|
'redirect_uri',
|
||||||
|
this.configService.get('SSO_REDIRECT_URI'),
|
||||||
|
);
|
||||||
|
return bodyFormData;
|
||||||
|
}
|
||||||
|
}
|
||||||
32
api/src/shared/database/database.module.ts
Normal file
32
api/src/shared/database/database.module.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { SSOUser, User } from 'src/model/entitites';
|
||||||
|
import { SsoUserRepository, UserRepository } from 'src/model/repositories';
|
||||||
|
|
||||||
|
const ENTITIES = [User, SSOUser];
|
||||||
|
const REPOSITORIES = [UserRepository, SsoUserRepository];
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
TypeOrmModule.forRootAsync({
|
||||||
|
useFactory: () => ({
|
||||||
|
type: 'mysql',
|
||||||
|
host: process.env.DATABASE_HOST,
|
||||||
|
port: parseInt(process.env.DATABASE_PORT) || 3306,
|
||||||
|
username: process.env.MYSQL_USER,
|
||||||
|
password: process.env.MYSQL_PASSWORD,
|
||||||
|
database: process.env.MYSQL_DATABASE,
|
||||||
|
synchronize: true,
|
||||||
|
autoLoadEntities: true,
|
||||||
|
retryAttempts: 5,
|
||||||
|
retryDelay: 10000,
|
||||||
|
logging: ['error'],
|
||||||
|
logger: 'file',
|
||||||
|
entities: [...ENTITIES],
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
providers: [...REPOSITORIES],
|
||||||
|
exports: [TypeOrmModule, ...REPOSITORIES],
|
||||||
|
})
|
||||||
|
export class DatabaseModule {}
|
||||||
@@ -32,7 +32,8 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"styles": [
|
"styles": [
|
||||||
"src/styles.scss"
|
"src/styles.scss",
|
||||||
|
"src/styles/ag.css"
|
||||||
],
|
],
|
||||||
"scripts": []
|
"scripts": []
|
||||||
},
|
},
|
||||||
|
|||||||
29
client/package-lock.json
generated
29
client/package-lock.json
generated
@@ -16,6 +16,7 @@
|
|||||||
"@angular/platform-browser": "^18.0.0",
|
"@angular/platform-browser": "^18.0.0",
|
||||||
"@angular/platform-browser-dynamic": "^18.0.0",
|
"@angular/platform-browser-dynamic": "^18.0.0",
|
||||||
"@angular/router": "^18.0.0",
|
"@angular/router": "^18.0.0",
|
||||||
|
"ag-grid-angular": "^32.1.0",
|
||||||
"rxjs": "~7.8.0",
|
"rxjs": "~7.8.0",
|
||||||
"tslib": "^2.3.0",
|
"tslib": "^2.3.0",
|
||||||
"zone.js": "~0.14.3"
|
"zone.js": "~0.14.3"
|
||||||
@@ -4433,6 +4434,34 @@
|
|||||||
"node": ">=8.9.0"
|
"node": ">=8.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ag-charts-types": {
|
||||||
|
"version": "10.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ag-charts-types/-/ag-charts-types-10.1.0.tgz",
|
||||||
|
"integrity": "sha512-pk9ft8hbgTXJ/thI/SEUR1BoauNplYExpcHh7tMOqVikoDsta1O15TB1ZL4XWnl4TPIzROBmONKsz7d8a2HBuQ==",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
|
"node_modules/ag-grid-angular": {
|
||||||
|
"version": "32.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ag-grid-angular/-/ag-grid-angular-32.1.0.tgz",
|
||||||
|
"integrity": "sha512-d21XbuSUvuy8Hp3m/ltU1dIjPRRB082+tNeKodf2Mwt0zNCY5H+Y1kSimRDnhU5KLK/3yIiCzpgAzwz6AmnxtQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.3.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@angular/common": ">= 16.0.0",
|
||||||
|
"@angular/core": ">= 16.0.0",
|
||||||
|
"ag-grid-community": "32.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ag-grid-community": {
|
||||||
|
"version": "32.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ag-grid-community/-/ag-grid-community-32.1.0.tgz",
|
||||||
|
"integrity": "sha512-RVvkjRH61nuCXwIqTKQPqNbKR+8cGBKw7S1qmmMXsy0pCBAJaQn4kL3v31hKHxDtV4bPscBXLFKGnKzHuss0GQ==",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"ag-charts-types": "10.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/agent-base": {
|
"node_modules/agent-base": {
|
||||||
"version": "7.1.1",
|
"version": "7.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz",
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
"@angular/platform-browser": "^18.0.0",
|
"@angular/platform-browser": "^18.0.0",
|
||||||
"@angular/platform-browser-dynamic": "^18.0.0",
|
"@angular/platform-browser-dynamic": "^18.0.0",
|
||||||
"@angular/router": "^18.0.0",
|
"@angular/router": "^18.0.0",
|
||||||
|
"ag-grid-angular": "^32.1.0",
|
||||||
"rxjs": "~7.8.0",
|
"rxjs": "~7.8.0",
|
||||||
"tslib": "^2.3.0",
|
"tslib": "^2.3.0",
|
||||||
"zone.js": "~0.14.3"
|
"zone.js": "~0.14.3"
|
||||||
|
|||||||
@@ -1,336 +1,10 @@
|
|||||||
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
|
<!-- <div class="content" style="width: 100%; height: 100%;"> -->
|
||||||
<!-- * * * * * * * * * * * The content below * * * * * * * * * * * -->
|
<!-- The AG Grid component, with Dimensions, CSS Theme, Row Data, and Column Definition -->
|
||||||
<!-- * * * * * * * * * * is only a placeholder * * * * * * * * * * -->
|
<ag-grid-angular
|
||||||
<!-- * * * * * * * * * * and can be replaced. * * * * * * * * * * -->
|
style="width: 100%; height: 100%;"
|
||||||
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
|
|
||||||
<!-- * * * * * * * * * Delete the template below * * * * * * * * * -->
|
|
||||||
<!-- * * * * * * * to get started with your project! * * * * * * * -->
|
|
||||||
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
|
|
||||||
|
|
||||||
<style>
|
[rowData]="rowData"
|
||||||
:host {
|
[columnDefs]="colDefs"
|
||||||
--bright-blue: oklch(51.01% 0.274 263.83);
|
[defaultColDef]="defaultColDef"
|
||||||
--electric-violet: oklch(53.18% 0.28 296.97);
|
/>
|
||||||
--french-violet: oklch(47.66% 0.246 305.88);
|
<!-- </div> -->
|
||||||
--vivid-pink: oklch(69.02% 0.277 332.77);
|
|
||||||
--hot-red: oklch(61.42% 0.238 15.34);
|
|
||||||
--orange-red: oklch(63.32% 0.24 31.68);
|
|
||||||
|
|
||||||
--gray-900: oklch(19.37% 0.006 300.98);
|
|
||||||
--gray-700: oklch(36.98% 0.014 302.71);
|
|
||||||
--gray-400: oklch(70.9% 0.015 304.04);
|
|
||||||
|
|
||||||
--red-to-pink-to-purple-vertical-gradient: linear-gradient(
|
|
||||||
180deg,
|
|
||||||
var(--orange-red) 0%,
|
|
||||||
var(--vivid-pink) 50%,
|
|
||||||
var(--electric-violet) 100%
|
|
||||||
);
|
|
||||||
|
|
||||||
--red-to-pink-to-purple-horizontal-gradient: linear-gradient(
|
|
||||||
90deg,
|
|
||||||
var(--orange-red) 0%,
|
|
||||||
var(--vivid-pink) 50%,
|
|
||||||
var(--electric-violet) 100%
|
|
||||||
);
|
|
||||||
|
|
||||||
--pill-accent: var(--bright-blue);
|
|
||||||
|
|
||||||
font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
|
||||||
Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
|
|
||||||
"Segoe UI Symbol";
|
|
||||||
box-sizing: border-box;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: 3.125rem;
|
|
||||||
color: var(--gray-900);
|
|
||||||
font-weight: 500;
|
|
||||||
line-height: 100%;
|
|
||||||
letter-spacing: -0.125rem;
|
|
||||||
margin: 0;
|
|
||||||
font-family: "Inter Tight", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
|
||||||
Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
|
|
||||||
"Segoe UI Symbol";
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin: 0;
|
|
||||||
color: var(--gray-700);
|
|
||||||
}
|
|
||||||
|
|
||||||
main {
|
|
||||||
width: 100%;
|
|
||||||
min-height: 100%;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
padding: 1rem;
|
|
||||||
box-sizing: inherit;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.angular-logo {
|
|
||||||
max-width: 9.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-around;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 700px;
|
|
||||||
margin-bottom: 3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content h1 {
|
|
||||||
margin-top: 1.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content p {
|
|
||||||
margin-top: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.divider {
|
|
||||||
width: 1px;
|
|
||||||
background: var(--red-to-pink-to-purple-vertical-gradient);
|
|
||||||
margin-inline: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pill-group {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: start;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 1.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pill {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
--pill-accent: var(--bright-blue);
|
|
||||||
background: color-mix(in srgb, var(--pill-accent) 5%, transparent);
|
|
||||||
color: var(--pill-accent);
|
|
||||||
padding-inline: 0.75rem;
|
|
||||||
padding-block: 0.375rem;
|
|
||||||
border-radius: 2.75rem;
|
|
||||||
border: 0;
|
|
||||||
transition: background 0.3s ease;
|
|
||||||
font-family: var(--inter-font);
|
|
||||||
font-size: 0.875rem;
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 500;
|
|
||||||
line-height: 1.4rem;
|
|
||||||
letter-spacing: -0.00875rem;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pill:hover {
|
|
||||||
background: color-mix(in srgb, var(--pill-accent) 15%, transparent);
|
|
||||||
}
|
|
||||||
|
|
||||||
.pill-group .pill:nth-child(6n + 1) {
|
|
||||||
--pill-accent: var(--bright-blue);
|
|
||||||
}
|
|
||||||
.pill-group .pill:nth-child(6n + 2) {
|
|
||||||
--pill-accent: var(--french-violet);
|
|
||||||
}
|
|
||||||
.pill-group .pill:nth-child(6n + 3),
|
|
||||||
.pill-group .pill:nth-child(6n + 4),
|
|
||||||
.pill-group .pill:nth-child(6n + 5) {
|
|
||||||
--pill-accent: var(--hot-red);
|
|
||||||
}
|
|
||||||
|
|
||||||
.pill-group svg {
|
|
||||||
margin-inline-start: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.social-links {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.73rem;
|
|
||||||
margin-top: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.social-links path {
|
|
||||||
transition: fill 0.3s ease;
|
|
||||||
fill: var(--gray-400);
|
|
||||||
}
|
|
||||||
|
|
||||||
.social-links a:hover svg path {
|
|
||||||
fill: var(--gray-900);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 650px) {
|
|
||||||
.content {
|
|
||||||
flex-direction: column;
|
|
||||||
width: max-content;
|
|
||||||
}
|
|
||||||
|
|
||||||
.divider {
|
|
||||||
height: 1px;
|
|
||||||
width: 100%;
|
|
||||||
background: var(--red-to-pink-to-purple-horizontal-gradient);
|
|
||||||
margin-block: 1.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<main class="main">
|
|
||||||
<div class="content">
|
|
||||||
<div class="left-side">
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 982 239"
|
|
||||||
fill="none"
|
|
||||||
class="angular-logo"
|
|
||||||
>
|
|
||||||
<g clip-path="url(#a)">
|
|
||||||
<path
|
|
||||||
fill="url(#b)"
|
|
||||||
d="M388.676 191.625h30.849L363.31 31.828h-35.758l-56.215 159.797h30.848l13.174-39.356h60.061l13.256 39.356Zm-65.461-62.675 21.602-64.311h1.227l21.602 64.311h-44.431Zm126.831-7.527v70.202h-28.23V71.839h27.002v20.374h1.392c2.782-6.71 7.2-12.028 13.255-15.956 6.056-3.927 13.584-5.89 22.503-5.89 8.264 0 15.465 1.8 21.684 5.318 6.137 3.518 10.964 8.673 14.319 15.382 3.437 6.71 5.074 14.81 4.992 24.383v76.175h-28.23v-71.92c0-8.019-2.046-14.237-6.219-18.819-4.173-4.5-9.819-6.791-17.102-6.791-4.91 0-9.328 1.063-13.174 3.272-3.846 2.128-6.792 5.237-9.001 9.328-2.046 4.009-3.191 8.918-3.191 14.728ZM589.233 239c-10.147 0-18.82-1.391-26.103-4.091-7.282-2.7-13.092-6.382-17.511-10.964-4.418-4.582-7.528-9.655-9.164-15.219l25.448-6.136c1.145 2.372 2.782 4.663 4.991 6.954 2.209 2.291 5.155 4.255 8.837 5.81 3.683 1.554 8.428 2.291 14.074 2.291 8.019 0 14.647-1.964 19.884-5.81 5.237-3.845 7.856-10.227 7.856-19.064v-22.665h-1.391c-1.473 2.946-3.601 5.892-6.383 9.001-2.782 3.109-6.464 5.645-10.965 7.691-4.582 2.046-10.228 3.109-17.101 3.109-9.165 0-17.511-2.209-25.039-6.545-7.446-4.337-13.42-10.883-17.757-19.474-4.418-8.673-6.628-19.473-6.628-32.565 0-13.091 2.21-24.301 6.628-33.383 4.419-9.082 10.311-15.955 17.839-20.7 7.528-4.746 15.874-7.037 25.039-7.037 7.037 0 12.846 1.145 17.347 3.518 4.582 2.373 8.182 5.236 10.883 8.51 2.7 3.272 4.746 6.382 6.137 9.327h1.554v-19.8h27.821v121.749c0 10.228-2.454 18.737-7.364 25.447-4.91 6.709-11.538 11.7-20.048 15.055-8.509 3.355-18.165 4.991-28.884 4.991Zm.245-71.266c5.974 0 11.047-1.473 15.302-4.337 4.173-2.945 7.446-7.118 9.573-12.519 2.21-5.482 3.274-12.027 3.274-19.637 0-7.609-1.064-14.155-3.274-19.8-2.127-5.646-5.318-10.064-9.491-13.255-4.174-3.11-9.329-4.746-15.384-4.746s-11.537 1.636-15.792 4.91c-4.173 3.272-7.365 7.772-9.492 13.418-2.128 5.727-3.191 12.191-3.191 19.392 0 7.2 1.063 13.745 3.273 19.228 2.127 5.482 5.318 9.736 9.573 12.764 4.174 3.027 9.41 4.582 15.629 4.582Zm141.56-26.51V71.839h28.23v119.786h-27.412v-21.273h-1.227c-2.7 6.709-7.119 12.191-13.338 16.446-6.137 4.255-13.747 6.382-22.748 6.382-7.855 0-14.81-1.718-20.783-5.237-5.974-3.518-10.72-8.591-14.075-15.382-3.355-6.709-5.073-14.891-5.073-24.464V71.839h28.312v71.921c0 7.609 2.046 13.664 6.219 18.083 4.173 4.5 9.655 6.709 16.365 6.709 4.173 0 8.183-.982 12.111-3.028 3.927-2.045 7.118-5.072 9.655-9.082 2.537-4.091 3.764-9.164 3.764-15.218Zm65.707-109.395v159.796h-28.23V31.828h28.23Zm44.841 162.169c-7.61 0-14.402-1.391-20.457-4.091-6.055-2.7-10.883-6.791-14.32-12.109-3.518-5.319-5.237-11.946-5.237-19.801 0-6.791 1.228-12.355 3.765-16.773 2.536-4.419 5.891-7.937 10.228-10.637 4.337-2.618 9.164-4.664 14.647-6.055 5.4-1.391 11.046-2.373 16.856-3.027 7.037-.737 12.683-1.391 17.102-1.964 4.337-.573 7.528-1.555 9.574-2.782 1.963-1.309 3.027-3.273 3.027-5.973v-.491c0-5.891-1.718-10.391-5.237-13.664-3.518-3.191-8.51-4.828-15.056-4.828-6.955 0-12.356 1.473-16.447 4.5-4.009 3.028-6.71 6.546-8.183 10.719l-26.348-3.764c2.046-7.282 5.483-13.336 10.31-18.328 4.746-4.909 10.638-8.59 17.511-11.045 6.955-2.455 14.565-3.682 22.912-3.682 5.809 0 11.537.654 17.265 2.045s10.965 3.6 15.711 6.71c4.746 3.109 8.51 7.282 11.455 12.6 2.864 5.318 4.337 11.946 4.337 19.883v80.184h-27.166v-16.446h-.9c-1.719 3.355-4.092 6.464-7.201 9.328-3.109 2.864-6.955 5.237-11.619 6.955-4.828 1.718-10.229 2.536-16.529 2.536Zm7.364-20.701c5.646 0 10.556-1.145 14.729-3.354 4.173-2.291 7.364-5.237 9.655-9.001 2.292-3.763 3.355-7.854 3.355-12.273v-14.155c-.9.737-2.373 1.391-4.5 2.046-2.128.654-4.419 1.145-7.037 1.636-2.619.491-5.155.9-7.692 1.227-2.537.328-4.746.655-6.628.901-4.173.572-8.019 1.472-11.292 2.781-3.355 1.31-5.973 3.11-7.855 5.401-1.964 2.291-2.864 5.318-2.864 8.918 0 5.237 1.882 9.164 5.728 11.782 3.682 2.782 8.51 4.091 14.401 4.091Zm64.643 18.328V71.839h27.412v19.965h1.227c2.21-6.955 5.974-12.274 11.292-16.038 5.319-3.763 11.456-5.645 18.329-5.645 1.555 0 3.355.082 5.237.163 1.964.164 3.601.328 4.91.573v25.938c-1.227-.41-3.109-.819-5.646-1.146a58.814 58.814 0 0 0-7.446-.49c-5.155 0-9.738 1.145-13.829 3.354-4.091 2.209-7.282 5.236-9.655 9.164-2.373 3.927-3.519 8.427-3.519 13.5v70.448h-28.312ZM222.077 39.192l-8.019 125.923L137.387 0l84.69 39.192Zm-53.105 162.825-57.933 33.056-57.934-33.056 11.783-28.556h92.301l11.783 28.556ZM111.039 62.675l30.357 73.803H80.681l30.358-73.803ZM7.937 165.115 0 39.192 84.69 0 7.937 165.115Z"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
fill="url(#c)"
|
|
||||||
d="M388.676 191.625h30.849L363.31 31.828h-35.758l-56.215 159.797h30.848l13.174-39.356h60.061l13.256 39.356Zm-65.461-62.675 21.602-64.311h1.227l21.602 64.311h-44.431Zm126.831-7.527v70.202h-28.23V71.839h27.002v20.374h1.392c2.782-6.71 7.2-12.028 13.255-15.956 6.056-3.927 13.584-5.89 22.503-5.89 8.264 0 15.465 1.8 21.684 5.318 6.137 3.518 10.964 8.673 14.319 15.382 3.437 6.71 5.074 14.81 4.992 24.383v76.175h-28.23v-71.92c0-8.019-2.046-14.237-6.219-18.819-4.173-4.5-9.819-6.791-17.102-6.791-4.91 0-9.328 1.063-13.174 3.272-3.846 2.128-6.792 5.237-9.001 9.328-2.046 4.009-3.191 8.918-3.191 14.728ZM589.233 239c-10.147 0-18.82-1.391-26.103-4.091-7.282-2.7-13.092-6.382-17.511-10.964-4.418-4.582-7.528-9.655-9.164-15.219l25.448-6.136c1.145 2.372 2.782 4.663 4.991 6.954 2.209 2.291 5.155 4.255 8.837 5.81 3.683 1.554 8.428 2.291 14.074 2.291 8.019 0 14.647-1.964 19.884-5.81 5.237-3.845 7.856-10.227 7.856-19.064v-22.665h-1.391c-1.473 2.946-3.601 5.892-6.383 9.001-2.782 3.109-6.464 5.645-10.965 7.691-4.582 2.046-10.228 3.109-17.101 3.109-9.165 0-17.511-2.209-25.039-6.545-7.446-4.337-13.42-10.883-17.757-19.474-4.418-8.673-6.628-19.473-6.628-32.565 0-13.091 2.21-24.301 6.628-33.383 4.419-9.082 10.311-15.955 17.839-20.7 7.528-4.746 15.874-7.037 25.039-7.037 7.037 0 12.846 1.145 17.347 3.518 4.582 2.373 8.182 5.236 10.883 8.51 2.7 3.272 4.746 6.382 6.137 9.327h1.554v-19.8h27.821v121.749c0 10.228-2.454 18.737-7.364 25.447-4.91 6.709-11.538 11.7-20.048 15.055-8.509 3.355-18.165 4.991-28.884 4.991Zm.245-71.266c5.974 0 11.047-1.473 15.302-4.337 4.173-2.945 7.446-7.118 9.573-12.519 2.21-5.482 3.274-12.027 3.274-19.637 0-7.609-1.064-14.155-3.274-19.8-2.127-5.646-5.318-10.064-9.491-13.255-4.174-3.11-9.329-4.746-15.384-4.746s-11.537 1.636-15.792 4.91c-4.173 3.272-7.365 7.772-9.492 13.418-2.128 5.727-3.191 12.191-3.191 19.392 0 7.2 1.063 13.745 3.273 19.228 2.127 5.482 5.318 9.736 9.573 12.764 4.174 3.027 9.41 4.582 15.629 4.582Zm141.56-26.51V71.839h28.23v119.786h-27.412v-21.273h-1.227c-2.7 6.709-7.119 12.191-13.338 16.446-6.137 4.255-13.747 6.382-22.748 6.382-7.855 0-14.81-1.718-20.783-5.237-5.974-3.518-10.72-8.591-14.075-15.382-3.355-6.709-5.073-14.891-5.073-24.464V71.839h28.312v71.921c0 7.609 2.046 13.664 6.219 18.083 4.173 4.5 9.655 6.709 16.365 6.709 4.173 0 8.183-.982 12.111-3.028 3.927-2.045 7.118-5.072 9.655-9.082 2.537-4.091 3.764-9.164 3.764-15.218Zm65.707-109.395v159.796h-28.23V31.828h28.23Zm44.841 162.169c-7.61 0-14.402-1.391-20.457-4.091-6.055-2.7-10.883-6.791-14.32-12.109-3.518-5.319-5.237-11.946-5.237-19.801 0-6.791 1.228-12.355 3.765-16.773 2.536-4.419 5.891-7.937 10.228-10.637 4.337-2.618 9.164-4.664 14.647-6.055 5.4-1.391 11.046-2.373 16.856-3.027 7.037-.737 12.683-1.391 17.102-1.964 4.337-.573 7.528-1.555 9.574-2.782 1.963-1.309 3.027-3.273 3.027-5.973v-.491c0-5.891-1.718-10.391-5.237-13.664-3.518-3.191-8.51-4.828-15.056-4.828-6.955 0-12.356 1.473-16.447 4.5-4.009 3.028-6.71 6.546-8.183 10.719l-26.348-3.764c2.046-7.282 5.483-13.336 10.31-18.328 4.746-4.909 10.638-8.59 17.511-11.045 6.955-2.455 14.565-3.682 22.912-3.682 5.809 0 11.537.654 17.265 2.045s10.965 3.6 15.711 6.71c4.746 3.109 8.51 7.282 11.455 12.6 2.864 5.318 4.337 11.946 4.337 19.883v80.184h-27.166v-16.446h-.9c-1.719 3.355-4.092 6.464-7.201 9.328-3.109 2.864-6.955 5.237-11.619 6.955-4.828 1.718-10.229 2.536-16.529 2.536Zm7.364-20.701c5.646 0 10.556-1.145 14.729-3.354 4.173-2.291 7.364-5.237 9.655-9.001 2.292-3.763 3.355-7.854 3.355-12.273v-14.155c-.9.737-2.373 1.391-4.5 2.046-2.128.654-4.419 1.145-7.037 1.636-2.619.491-5.155.9-7.692 1.227-2.537.328-4.746.655-6.628.901-4.173.572-8.019 1.472-11.292 2.781-3.355 1.31-5.973 3.11-7.855 5.401-1.964 2.291-2.864 5.318-2.864 8.918 0 5.237 1.882 9.164 5.728 11.782 3.682 2.782 8.51 4.091 14.401 4.091Zm64.643 18.328V71.839h27.412v19.965h1.227c2.21-6.955 5.974-12.274 11.292-16.038 5.319-3.763 11.456-5.645 18.329-5.645 1.555 0 3.355.082 5.237.163 1.964.164 3.601.328 4.91.573v25.938c-1.227-.41-3.109-.819-5.646-1.146a58.814 58.814 0 0 0-7.446-.49c-5.155 0-9.738 1.145-13.829 3.354-4.091 2.209-7.282 5.236-9.655 9.164-2.373 3.927-3.519 8.427-3.519 13.5v70.448h-28.312ZM222.077 39.192l-8.019 125.923L137.387 0l84.69 39.192Zm-53.105 162.825-57.933 33.056-57.934-33.056 11.783-28.556h92.301l11.783 28.556ZM111.039 62.675l30.357 73.803H80.681l30.358-73.803ZM7.937 165.115 0 39.192 84.69 0 7.937 165.115Z"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
<defs>
|
|
||||||
<radialGradient
|
|
||||||
id="c"
|
|
||||||
cx="0"
|
|
||||||
cy="0"
|
|
||||||
r="1"
|
|
||||||
gradientTransform="rotate(118.122 171.182 60.81) scale(205.794)"
|
|
||||||
gradientUnits="userSpaceOnUse"
|
|
||||||
>
|
|
||||||
<stop stop-color="#FF41F8" />
|
|
||||||
<stop offset=".707" stop-color="#FF41F8" stop-opacity=".5" />
|
|
||||||
<stop offset="1" stop-color="#FF41F8" stop-opacity="0" />
|
|
||||||
</radialGradient>
|
|
||||||
<linearGradient
|
|
||||||
id="b"
|
|
||||||
x1="0"
|
|
||||||
x2="982"
|
|
||||||
y1="192"
|
|
||||||
y2="192"
|
|
||||||
gradientUnits="userSpaceOnUse"
|
|
||||||
>
|
|
||||||
<stop stop-color="#F0060B" />
|
|
||||||
<stop offset="0" stop-color="#F0070C" />
|
|
||||||
<stop offset=".526" stop-color="#CC26D5" />
|
|
||||||
<stop offset="1" stop-color="#7702FF" />
|
|
||||||
</linearGradient>
|
|
||||||
<clipPath id="a"><path fill="#fff" d="M0 0h982v239H0z" /></clipPath>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
||||||
<h1>Hello, {{ title }}</h1>
|
|
||||||
<p>Congratulations! Your app is running. 🎉</p>
|
|
||||||
</div>
|
|
||||||
<div class="divider" role="separator" aria-label="Divider"></div>
|
|
||||||
<div class="right-side">
|
|
||||||
<div class="pill-group">
|
|
||||||
@for (item of [
|
|
||||||
{ title: 'Explore the Docs', link: 'https://angular.dev' },
|
|
||||||
{ title: 'Learn with Tutorials', link: 'https://angular.dev/tutorials' },
|
|
||||||
{ title: 'CLI Docs', link: 'https://angular.dev/tools/cli' },
|
|
||||||
{ title: 'Angular Language Service', link: 'https://angular.dev/tools/language-service' },
|
|
||||||
{ title: 'Angular DevTools', link: 'https://angular.dev/tools/devtools' },
|
|
||||||
]; track item.title) {
|
|
||||||
<a
|
|
||||||
class="pill"
|
|
||||||
[href]="item.link"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener"
|
|
||||||
>
|
|
||||||
<span>{{ item.title }}</span>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
height="14"
|
|
||||||
viewBox="0 -960 960 960"
|
|
||||||
width="14"
|
|
||||||
fill="currentColor"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h280v80H200v560h560v-280h80v280q0 33-23.5 56.5T760-120H200Zm188-212-56-56 372-372H560v-80h280v280h-80v-144L388-332Z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</a>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<div class="social-links">
|
|
||||||
<a
|
|
||||||
href="https://github.com/angular/angular"
|
|
||||||
aria-label="Github"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
width="25"
|
|
||||||
height="24"
|
|
||||||
viewBox="0 0 25 24"
|
|
||||||
fill="none"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
alt="Github"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M12.3047 0C5.50634 0 0 5.50942 0 12.3047C0 17.7423 3.52529 22.3535 8.41332 23.9787C9.02856 24.0946 9.25414 23.7142 9.25414 23.3871C9.25414 23.0949 9.24389 22.3207 9.23876 21.2953C5.81601 22.0377 5.09414 19.6444 5.09414 19.6444C4.53427 18.2243 3.72524 17.8449 3.72524 17.8449C2.61064 17.082 3.81137 17.0973 3.81137 17.0973C5.04697 17.1835 5.69604 18.3647 5.69604 18.3647C6.79321 20.2463 8.57636 19.7029 9.27978 19.3881C9.39052 18.5924 9.70736 18.0499 10.0591 17.7423C7.32641 17.4347 4.45429 16.3765 4.45429 11.6618C4.45429 10.3185 4.9311 9.22133 5.72065 8.36C5.58222 8.04931 5.16694 6.79833 5.82831 5.10337C5.82831 5.10337 6.85883 4.77319 9.2121 6.36459C10.1965 6.09082 11.2424 5.95546 12.2883 5.94931C13.3342 5.95546 14.3801 6.09082 15.3644 6.36459C17.7023 4.77319 18.7328 5.10337 18.7328 5.10337C19.3942 6.79833 18.9789 8.04931 18.8559 8.36C19.6403 9.22133 20.1171 10.3185 20.1171 11.6618C20.1171 16.3888 17.2409 17.4296 14.5031 17.7321C14.9338 18.1012 15.3337 18.8559 15.3337 20.0084C15.3337 21.6552 15.3183 22.978 15.3183 23.3779C15.3183 23.7009 15.5336 24.0854 16.1642 23.9623C21.0871 22.3484 24.6094 17.7341 24.6094 12.3047C24.6094 5.50942 19.0999 0 12.3047 0Z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
href="https://twitter.com/angular"
|
|
||||||
aria-label="Twitter"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
width="24"
|
|
||||||
height="24"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="none"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
alt="Twitter"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
href="https://www.youtube.com/channel/UCbn1OgGei-DV7aSRo_HaAiw"
|
|
||||||
aria-label="Youtube"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
width="29"
|
|
||||||
height="20"
|
|
||||||
viewBox="0 0 29 20"
|
|
||||||
fill="none"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
alt="Youtube"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fill-rule="evenodd"
|
|
||||||
clip-rule="evenodd"
|
|
||||||
d="M27.4896 1.52422C27.9301 1.96749 28.2463 2.51866 28.4068 3.12258C29.0004 5.35161 29.0004 10 29.0004 10C29.0004 10 29.0004 14.6484 28.4068 16.8774C28.2463 17.4813 27.9301 18.0325 27.4896 18.4758C27.0492 18.9191 26.5 19.2389 25.8972 19.4032C23.6778 20 14.8068 20 14.8068 20C14.8068 20 5.93586 20 3.71651 19.4032C3.11363 19.2389 2.56449 18.9191 2.12405 18.4758C1.68361 18.0325 1.36732 17.4813 1.20683 16.8774C0.613281 14.6484 0.613281 10 0.613281 10C0.613281 10 0.613281 5.35161 1.20683 3.12258C1.36732 2.51866 1.68361 1.96749 2.12405 1.52422C2.56449 1.08095 3.11363 0.76113 3.71651 0.596774C5.93586 0 14.8068 0 14.8068 0C14.8068 0 23.6778 0 25.8972 0.596774C26.5 0.76113 27.0492 1.08095 27.4896 1.52422ZM19.3229 10L11.9036 5.77905V14.221L19.3229 10Z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
|
|
||||||
<!-- * * * * * * * * * * * The content above * * * * * * * * * * * * -->
|
|
||||||
<!-- * * * * * * * * * * is only a placeholder * * * * * * * * * * * -->
|
|
||||||
<!-- * * * * * * * * * * and can be replaced. * * * * * * * * * * * -->
|
|
||||||
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
|
|
||||||
<!-- * * * * * * * * * * End of Placeholder * * * * * * * * * * * * -->
|
|
||||||
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
|
|
||||||
|
|
||||||
|
|
||||||
<router-outlet />
|
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
:host {
|
||||||
|
display: flex;
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
}
|
||||||
@@ -1,17 +1,25 @@
|
|||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { Component, inject } from '@angular/core';
|
import { Component, inject, LOCALE_ID } from '@angular/core';
|
||||||
import { RouterOutlet } from '@angular/router';
|
import { RouterOutlet } from '@angular/router';
|
||||||
|
import { AgGridAngular } from 'ag-grid-angular'; // Angular Data Grid Component
|
||||||
|
import { ColDef } from 'ag-grid-community'; // Column Definition Type Interface
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [RouterOutlet],
|
imports: [RouterOutlet, AgGridAngular],
|
||||||
|
providers: [[{ provide: LOCALE_ID, useValue: 'de-DE' }]],
|
||||||
templateUrl: './app.component.html',
|
templateUrl: './app.component.html',
|
||||||
styleUrl: './app.component.scss'
|
styleUrl: './app.component.scss'
|
||||||
})
|
})
|
||||||
export class AppComponent {
|
export class AppComponent {
|
||||||
title = 'client';
|
title = 'client';
|
||||||
|
|
||||||
|
defaultColDef: ColDef = {
|
||||||
|
flex: 1,
|
||||||
|
editable: true
|
||||||
|
};
|
||||||
|
|
||||||
private http: HttpClient = inject(HttpClient);
|
private http: HttpClient = inject(HttpClient);
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -24,4 +32,31 @@ export class AppComponent {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rowData = [
|
||||||
|
{ make: "Tesla", model: "Model Y", price: 64950, electric: true },
|
||||||
|
{ make: "Ford", model: "F-Series", price: 33850, electric: false },
|
||||||
|
{ make: "Toyota", model: "Corolla", price: 29600, electric: false },
|
||||||
|
];
|
||||||
|
|
||||||
|
// Column Definitions: Defines the columns to be displayed.
|
||||||
|
colDefs: ColDef[] = [
|
||||||
|
{ field: "make" },
|
||||||
|
{
|
||||||
|
field: "model",
|
||||||
|
cellEditor: 'agSelectCellEditor',
|
||||||
|
singleClickEdit: true,
|
||||||
|
cellEditorParams: {
|
||||||
|
values: ['English', 'Spanish', 'French', 'Portuguese', '(other)'],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ field: "price", type: 'number'
|
||||||
|
// cellEditor: 'agDateCellEditor',
|
||||||
|
// cellEditorParams: {
|
||||||
|
// min: '2000-01-01',
|
||||||
|
// max: '2019-12-31',
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
{ field: "electric", editable: true }
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
import { bootstrapApplication } from '@angular/platform-browser';
|
import { bootstrapApplication } from '@angular/platform-browser';
|
||||||
import { appConfig } from './app/app.config';
|
import { appConfig } from './app/app.config';
|
||||||
import { AppComponent } from './app/app.component';
|
import { AppComponent } from './app/app.component';
|
||||||
|
import { registerLocaleData } from '@angular/common';
|
||||||
|
import localeDe from '@angular/common/locales/de';
|
||||||
|
import localeDeExtra from '@angular/common/locales/extra/de';
|
||||||
|
|
||||||
|
registerLocaleData(localeDe, 'de-DE', localeDeExtra);
|
||||||
|
|
||||||
|
|
||||||
bootstrapApplication(AppComponent, appConfig)
|
bootstrapApplication(AppComponent, appConfig)
|
||||||
.catch((err) => console.error(err));
|
.catch((err) => console.error(err));
|
||||||
|
|||||||
@@ -1 +1,13 @@
|
|||||||
/* You can add global styles to this file, and also import other style files */
|
/* You can add global styles to this file, and also import other style files */
|
||||||
|
html, body {
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
overflow: hidden;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Core Data Grid CSS */
|
||||||
|
@import "ag-grid-community/styles/ag-grid.css";
|
||||||
|
/* Quartz Theme Specific CSS */
|
||||||
|
@import 'ag-grid-community/styles/ag-theme-quartz.css';
|
||||||
403
client/src/styles/ag.css
Normal file
403
client/src/styles/ag.css
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user