Compare commits
7 Commits
17-homepag
...
dev
Author | SHA1 | Date |
---|---|---|
Michał Sieciechowicz | 1422d96c80 | |
Michał Sieciechowicz | f52e611c67 | |
Michał Sieciechowicz | cbca7cb582 | |
Michał Sieciechowicz | dc5cb30510 | |
Michał Sieciechowicz | f57d6d81f0 | |
Michał Sieciechowicz | f0015a1961 | |
Michał Sieciechowicz | 6fcad220de |
|
@ -0,0 +1,198 @@
|
|||
pipeline {
|
||||
agent any
|
||||
|
||||
environment {
|
||||
DatabaseUrl = sh(returnStdout: true, script: 'bash /var/lib/jenkins/variables/CureNet/var.sh ${BRANCH_NAME} DatabaseUrl').trim()
|
||||
ProjectPath = sh(returnStdout: true, script: 'bash /var/lib/jenkins/variables/CureNet/var.sh ${BRANCH_NAME} ProjectPath').trim()
|
||||
DatabaseUrlTesting = sh(returnStdout: true, script: 'bash /var/lib/jenkins/variables/CureNet/var.sh master DatabaseUrl').trim()
|
||||
}
|
||||
|
||||
stages {
|
||||
stage('Pull') {
|
||||
steps {
|
||||
git(url: 'http://git.fufle.net/Community/CureNet.git', branch: '${BRANCH_NAME}', credentialsId: '799188a2-c281-4e4c-b78f-c82132ea792b', poll: true)
|
||||
}
|
||||
}
|
||||
|
||||
stage('Approve') {
|
||||
steps {
|
||||
echo "http://git.fufle.net/Community/CureNet/commit/${env.GIT_COMMIT}"
|
||||
script {
|
||||
if (env.BRANCH_NAME == "prod") {
|
||||
echo "http://git.fufle.net/Community/CureNet/compare/prod...staging"
|
||||
input(message: 'Deploy to production?', id: 'A', ok: 'Yes', submitter: 'deploy', submitterParameter: 'a')
|
||||
}
|
||||
if (env.BRANCH_NAME == "staging") {
|
||||
echo "http://git.fufle.net/Community/CureNet/compare/staging...beta"
|
||||
input(message: 'Deploy to staging instance?', id: 'A', ok: 'Yes', submitter: 'deploy', submitterParameter: 'a')
|
||||
}
|
||||
if (env.BRANCH_NAME == "beta") {
|
||||
echo "http://git.fufle.net/Community/CureNet/compare/beta...dev"
|
||||
input(message: 'Deploy to beta instance?', id: 'A', ok: 'Yes', submitter: 'deploy', submitterParameter: 'a')
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
stage('Install') {
|
||||
parallel {
|
||||
stage('Frontend') {
|
||||
steps {
|
||||
dir(path: 'src/frontend') {
|
||||
sh 'sed -i "s/-1001/${BUILD_ID}/g" src/environments/*.ts'
|
||||
sh 'Timestamp=`php8.0 -r "echo time();"` && sed -i "s/-1002/${Timestamp}/g" src/environments/*.ts'
|
||||
sh 'npm ci'
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
stage('Backend') {
|
||||
steps {
|
||||
dir(path: 'src/backend') {
|
||||
sh 'php8.0 /usr/local/bin/composer install'
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
stage('Configure for tests') {
|
||||
parallel {
|
||||
stage('Frontend') {
|
||||
steps {
|
||||
dir(path: 'src/frontend') {
|
||||
sh 'echo skip'
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
stage('Backend') {
|
||||
steps {
|
||||
dir(path: 'src/backend') {
|
||||
sh 'APP_SECRET=`php8.0 -r "echo md5(\\"branch=${BRANCH_NAME};\\");"` && sed -i "s/APP_SECRET=.*/APP_SECRET=${APP_SECRET}/g" .env'
|
||||
sh '''sed -i 's/DATABASE_URL=.*/DATABASE_URL=${DatabaseUrlTesting}/g' .env.test'''
|
||||
sh 'php8.0 /usr/local/bin/composer dump-env test'
|
||||
sh 'php8.0 bin/console doctrine:schema:update --force';
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
stage('Test') {
|
||||
parallel {
|
||||
stage('Frontend') {
|
||||
steps {
|
||||
dir(path: 'src/frontend') {
|
||||
sh 'ng lint'
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
stage('Backend') {
|
||||
steps {
|
||||
dir(path: 'src/backend') {
|
||||
sh 'php8.0 vendor/bin/phpunit'
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
stage('Configure') {
|
||||
parallel {
|
||||
stage('Frontend') {
|
||||
steps {
|
||||
dir(path: 'src/frontend') {
|
||||
sh 'echo skip'
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
stage('Backend') {
|
||||
steps {
|
||||
dir(path: 'src/backend') {
|
||||
sh '''APP_SECRET=`php8.0 -r "echo md5(\\"branch=${BRANCH_NAME};\\");"` && sed -i 's/APP_SECRET=.*/APP_SECRET=${APP_SECRET}/g' .env'''
|
||||
sh '''sed -i 's/DATABASE_URL=.*/DATABASE_URL=${DatabaseUrl}/g' .env.dev'''
|
||||
sh 'php8.0 /usr/local/bin/composer dump-env dev'
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
stage('Build') {
|
||||
parallel {
|
||||
stage('Frontend') {
|
||||
steps {
|
||||
dir(path: 'src/frontend') {
|
||||
sh 'ng b -c production'
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
stage('Backend') {
|
||||
steps {
|
||||
dir(path: 'src/backend') {
|
||||
sh 'echo skip'
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
stage('Deploy') {
|
||||
when {
|
||||
anyOf {
|
||||
branch 'master'
|
||||
branch 'staging'
|
||||
branch 'beta'
|
||||
branch 'dev'
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
parallel {
|
||||
stage('Frontend') {
|
||||
steps {
|
||||
dir(path: 'src/frontend') {
|
||||
sh 'ssh web@fufle.net touch ${ProjectPath}/frontend/REMOVEME'
|
||||
sh 'ssh web@fufle.net rm -rf ${ProjectPath}/frontend/*'
|
||||
sh 'scp -r dist/* web@fufle.net:${ProjectPath}/frontend/'
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
stage('Backend') {
|
||||
steps {
|
||||
dir(path: 'src/backend') {
|
||||
sh 'ssh web@fufle.net touch ${ProjectPath}/backend/REMOVEME'
|
||||
sh 'ssh web@fufle.net rm -rf ${ProjectPath}/backend/*'
|
||||
sh 'scp -r ./* web@fufle.net:${ProjectPath}/backend/'
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -19,9 +19,7 @@ services:
|
|||
- ./logs:/var/log
|
||||
- vendor:/web/backend/vendor
|
||||
- var:/web/backend/var
|
||||
- nodemodules:/web/frontend/node_modules
|
||||
volumes:
|
||||
nodemodules:
|
||||
vendor:
|
||||
var:
|
||||
nodemodules:
|
|
@ -1,8 +1,7 @@
|
|||
#!/bin/bash
|
||||
|
||||
echo "> Configure cache"
|
||||
mkdir -p /tmp/app/{vendor/node_modules}
|
||||
ln -s /tmp/app/node_modules /web/frontend/node_modules
|
||||
mkdir -p /tmp/app/vendor
|
||||
ln -s /tmp/app/vendor /web/backend/vendor
|
||||
|
||||
echo "> Configure user"
|
||||
|
@ -46,10 +45,14 @@ echo "> Configure symfony"
|
|||
cd /web/backend
|
||||
composer install
|
||||
php bin/console doctrine:schema:update --force
|
||||
php php bin/console lexik:jwt:generate-keypair --skip-if-exists
|
||||
|
||||
echo "> Configure angular"
|
||||
cd /web/frontend
|
||||
su developer -c 'time npm ci'
|
||||
time su developer -c 'npm ci'
|
||||
|
||||
echo "> Starting angular"
|
||||
su developer -c 'ng serve --disable-host-check --host 0.0.0.0 --poll'
|
||||
|
||||
echo "> Waiting"
|
||||
sleep Infinity
|
|
@ -30,3 +30,9 @@ APP_SECRET=0fc8d6b67b9f1100b3eb3e3c80d36fda
|
|||
# DATABASE_URL="mysql://db_user:db_password@127.0.0.1:3306/db_name?serverVersion=5.7"
|
||||
DATABASE_URL="postgresql://db_user:db_password@127.0.0.1:5432/db_name?serverVersion=13&charset=utf8"
|
||||
###< doctrine/doctrine-bundle ###
|
||||
|
||||
###> lexik/jwt-authentication-bundle ###
|
||||
JWT_SECRET_KEY=%kernel.project_dir%/config/jwt/private.pem
|
||||
JWT_PUBLIC_KEY=%kernel.project_dir%/config/jwt/public.pem
|
||||
JWT_PASSPHRASE=8270442e040a48fd42967bf1690d5dba
|
||||
###< lexik/jwt-authentication-bundle ###
|
||||
|
|
|
@ -4,3 +4,4 @@ APP_SECRET='$ecretf0rt3st'
|
|||
SYMFONY_DEPRECATIONS_HELPER=999999
|
||||
PANTHER_APP_ENV=panther
|
||||
PANTHER_ERROR_SCREENSHOT_DIR=./var/error-screenshots
|
||||
DATABASE_URL="postgresql://db_user:db_password@127.0.0.1:5432/db_name?serverVersion=13&charset=utf8"
|
||||
|
|
|
@ -18,3 +18,7 @@
|
|||
/phpunit.xml
|
||||
.phpunit.result.cache
|
||||
###< phpunit/phpunit ###
|
||||
|
||||
###> lexik/jwt-authentication-bundle ###
|
||||
/config/jwt/*.pem
|
||||
###< lexik/jwt-authentication-bundle ###
|
||||
|
|
|
@ -12,8 +12,10 @@
|
|||
"doctrine/doctrine-bundle": "^2.4",
|
||||
"doctrine/doctrine-migrations-bundle": "^3.1",
|
||||
"doctrine/orm": "^2.9",
|
||||
"lexik/jwt-authentication-bundle": "^2.12",
|
||||
"phpdocumentor/reflection-docblock": "^5.2",
|
||||
"sensio/framework-extra-bundle": "^6.1",
|
||||
"symfony-bundles/json-request-bundle": "^4.0",
|
||||
"symfony/asset": "5.3.*",
|
||||
"symfony/console": "5.3.*",
|
||||
"symfony/dotenv": "5.3.*",
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "e3fa1ece831d58dd0515e0413472408f",
|
||||
"content-hash": "7f30e61383bffabaf547a9160e26d9c1",
|
||||
"packages": [
|
||||
{
|
||||
"name": "composer/package-versions-deprecated",
|
||||
|
@ -1653,6 +1653,252 @@
|
|||
],
|
||||
"time": "2021-07-09T11:58:40+00:00"
|
||||
},
|
||||
{
|
||||
"name": "lcobucci/clock",
|
||||
"version": "2.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/lcobucci/clock.git",
|
||||
"reference": "353d83fe2e6ae95745b16b3d911813df6a05bfb3"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/lcobucci/clock/zipball/353d83fe2e6ae95745b16b3d911813df6a05bfb3",
|
||||
"reference": "353d83fe2e6ae95745b16b3d911813df6a05bfb3",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.4 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"infection/infection": "^0.17",
|
||||
"lcobucci/coding-standard": "^6.0",
|
||||
"phpstan/extension-installer": "^1.0",
|
||||
"phpstan/phpstan": "^0.12",
|
||||
"phpstan/phpstan-deprecation-rules": "^0.12",
|
||||
"phpstan/phpstan-phpunit": "^0.12",
|
||||
"phpstan/phpstan-strict-rules": "^0.12",
|
||||
"phpunit/php-code-coverage": "9.1.4",
|
||||
"phpunit/phpunit": "9.3.7"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Lcobucci\\Clock\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Luís Cobucci",
|
||||
"email": "lcobucci@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "Yet another clock abstraction",
|
||||
"support": {
|
||||
"issues": "https://github.com/lcobucci/clock/issues",
|
||||
"source": "https://github.com/lcobucci/clock/tree/2.0.x"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/lcobucci",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://www.patreon.com/lcobucci",
|
||||
"type": "patreon"
|
||||
}
|
||||
],
|
||||
"time": "2020-08-27T18:56:02+00:00"
|
||||
},
|
||||
{
|
||||
"name": "lcobucci/jwt",
|
||||
"version": "4.1.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/lcobucci/jwt.git",
|
||||
"reference": "71cf170102c8371ccd933fa4df6252086d144de6"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/lcobucci/jwt/zipball/71cf170102c8371ccd933fa4df6252086d144de6",
|
||||
"reference": "71cf170102c8371ccd933fa4df6252086d144de6",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-hash": "*",
|
||||
"ext-json": "*",
|
||||
"ext-mbstring": "*",
|
||||
"ext-openssl": "*",
|
||||
"ext-sodium": "*",
|
||||
"lcobucci/clock": "^2.0",
|
||||
"php": "^7.4 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"infection/infection": "^0.21",
|
||||
"lcobucci/coding-standard": "^6.0",
|
||||
"mikey179/vfsstream": "^1.6.7",
|
||||
"phpbench/phpbench": "^1.0@alpha",
|
||||
"phpstan/extension-installer": "^1.0",
|
||||
"phpstan/phpstan": "^0.12",
|
||||
"phpstan/phpstan-deprecation-rules": "^0.12",
|
||||
"phpstan/phpstan-phpunit": "^0.12",
|
||||
"phpstan/phpstan-strict-rules": "^0.12",
|
||||
"phpunit/php-invoker": "^3.1",
|
||||
"phpunit/phpunit": "^9.5"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Lcobucci\\JWT\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-3-Clause"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Luís Cobucci",
|
||||
"email": "lcobucci@gmail.com",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "A simple library to work with JSON Web Token and JSON Web Signature",
|
||||
"keywords": [
|
||||
"JWS",
|
||||
"jwt"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/lcobucci/jwt/issues",
|
||||
"source": "https://github.com/lcobucci/jwt/tree/4.1.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/lcobucci",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://www.patreon.com/lcobucci",
|
||||
"type": "patreon"
|
||||
}
|
||||
],
|
||||
"time": "2021-03-23T23:53:08+00:00"
|
||||
},
|
||||
{
|
||||
"name": "lexik/jwt-authentication-bundle",
|
||||
"version": "v2.12.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/lexik/LexikJWTAuthenticationBundle.git",
|
||||
"reference": "7fb85afb1a63bb7e518a369baa355599f822ff43"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/lexik/LexikJWTAuthenticationBundle/zipball/7fb85afb1a63bb7e518a369baa355599f822ff43",
|
||||
"reference": "7fb85afb1a63bb7e518a369baa355599f822ff43",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-openssl": "*",
|
||||
"lcobucci/jwt": "^3.4|^4.0",
|
||||
"namshi/jose": "^7.2",
|
||||
"php": ">=7.1",
|
||||
"symfony/deprecation-contracts": "^2.4",
|
||||
"symfony/framework-bundle": "^4.4|^5.1",
|
||||
"symfony/security-bundle": "^4.4|^5.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/browser-kit": "^4.4|^5.1",
|
||||
"symfony/console": "^4.4|^5.1",
|
||||
"symfony/dom-crawler": "^4.4|^5.1",
|
||||
"symfony/phpunit-bridge": "^4.4|^5.1",
|
||||
"symfony/var-dumper": "^4.4|^5.1",
|
||||
"symfony/yaml": "^4.4|^5.1"
|
||||
},
|
||||
"suggest": {
|
||||
"gesdinet/jwt-refresh-token-bundle": "Implements a refresh token system over Json Web Tokens in Symfony",
|
||||
"spomky-labs/lexik-jose-bridge": "Provides a JWT Token encoder with encryption support"
|
||||
},
|
||||
"type": "symfony-bundle",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Lexik\\Bundle\\JWTAuthenticationBundle\\": ""
|
||||
},
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Jeremy Barthe",
|
||||
"email": "j.barthe@lexik.fr",
|
||||
"homepage": "https://github.com/jeremyb"
|
||||
},
|
||||
{
|
||||
"name": "Nicolas Cabot",
|
||||
"email": "n.cabot@lexik.fr",
|
||||
"homepage": "https://github.com/slashfan"
|
||||
},
|
||||
{
|
||||
"name": "Cedric Girard",
|
||||
"email": "c.girard@lexik.fr",
|
||||
"homepage": "https://github.com/cedric-g"
|
||||
},
|
||||
{
|
||||
"name": "Dev Lexik",
|
||||
"email": "dev@lexik.fr",
|
||||
"homepage": "https://github.com/lexik"
|
||||
},
|
||||
{
|
||||
"name": "Robin Chalas",
|
||||
"email": "robin.chalas@gmail.com",
|
||||
"homepage": "https://github.com/chalasr"
|
||||
},
|
||||
{
|
||||
"name": "Lexik Community",
|
||||
"homepage": "https://github.com/lexik/LexikJWTAuthenticationBundle/graphs/contributors"
|
||||
}
|
||||
],
|
||||
"description": "This bundle provides JWT authentication for your Symfony REST API",
|
||||
"homepage": "https://github.com/lexik/LexikJWTAuthenticationBundle",
|
||||
"keywords": [
|
||||
"Authentication",
|
||||
"JWS",
|
||||
"api",
|
||||
"bundle",
|
||||
"jwt",
|
||||
"rest",
|
||||
"symfony"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/lexik/LexikJWTAuthenticationBundle/issues",
|
||||
"source": "https://github.com/lexik/LexikJWTAuthenticationBundle/tree/v2.12.3"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/chalasr",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/lexik/jwt-authentication-bundle",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2021-07-07T09:06:05+00:00"
|
||||
},
|
||||
{
|
||||
"name": "monolog/monolog",
|
||||
"version": "2.3.1",
|
||||
|
@ -1749,6 +1995,73 @@
|
|||
],
|
||||
"time": "2021-07-14T11:56:39+00:00"
|
||||
},
|
||||
{
|
||||
"name": "namshi/jose",
|
||||
"version": "7.2.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/namshi/jose.git",
|
||||
"reference": "89a24d7eb3040e285dd5925fcad992378b82bcff"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/namshi/jose/zipball/89a24d7eb3040e285dd5925fcad992378b82bcff",
|
||||
"reference": "89a24d7eb3040e285dd5925fcad992378b82bcff",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-date": "*",
|
||||
"ext-hash": "*",
|
||||
"ext-json": "*",
|
||||
"ext-pcre": "*",
|
||||
"ext-spl": "*",
|
||||
"php": ">=5.5",
|
||||
"symfony/polyfill-php56": "^1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpseclib/phpseclib": "^2.0",
|
||||
"phpunit/phpunit": "^4.5|^5.0",
|
||||
"satooshi/php-coveralls": "^1.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-openssl": "Allows to use OpenSSL as crypto engine.",
|
||||
"phpseclib/phpseclib": "Allows to use Phpseclib as crypto engine, use version ^2.0."
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Namshi\\JOSE\\": "src/Namshi/JOSE/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Alessandro Nadalin",
|
||||
"email": "alessandro.nadalin@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Alessandro Cinelli (cirpo)",
|
||||
"email": "alessandro.cinelli@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "JSON Object Signing and Encryption library for PHP.",
|
||||
"keywords": [
|
||||
"JSON Web Signature",
|
||||
"JSON Web Token",
|
||||
"JWS",
|
||||
"json",
|
||||
"jwt",
|
||||
"token"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/namshi/jose/issues",
|
||||
"source": "https://github.com/namshi/jose/tree/master"
|
||||
},
|
||||
"time": "2016-12-05T07:27:31+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpdocumentor/reflection-common",
|
||||
"version": "2.2.0",
|
||||
|
@ -2235,6 +2548,68 @@
|
|||
},
|
||||
"time": "2021-05-31T10:40:46+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony-bundles/json-request-bundle",
|
||||
"version": "4.0.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony-bundles/json-request-bundle.git",
|
||||
"reference": "53af1ff1df5457ea16d18cebab1309e667ea86c4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony-bundles/json-request-bundle/zipball/53af1ff1df5457ea16d18cebab1309e667ea86c4",
|
||||
"reference": "53af1ff1df5457ea16d18cebab1309e667ea86c4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"php": ">=7.4 || ^8.0",
|
||||
"symfony/framework-bundle": "^5.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/annotations": "^1.0",
|
||||
"phpstan/phpstan": "^0.12",
|
||||
"phpunit/php-code-coverage": "^9.2",
|
||||
"phpunit/phpunit": "^9.5",
|
||||
"symfony/browser-kit": "^5.2",
|
||||
"symfony/yaml": "^5.2"
|
||||
},
|
||||
"type": "symfony-bundle",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "4.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"SymfonyBundles\\JsonRequestBundle\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Dmitry Khaperets",
|
||||
"email": "khaperets@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "Symfony JsonRequest Bundle",
|
||||
"homepage": "https://github.com/symfony-bundles/json-request-bundle",
|
||||
"keywords": [
|
||||
"angular",
|
||||
"bundle",
|
||||
"json",
|
||||
"symfony"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/symfony-bundles/json-request-bundle/issues",
|
||||
"source": "https://github.com/symfony-bundles/json-request-bundle/tree/4.0.5"
|
||||
},
|
||||
"time": "2021-02-16T11:16:20+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/asset",
|
||||
"version": "v5.3.2",
|
||||
|
@ -5186,6 +5561,74 @@
|
|||
],
|
||||
"time": "2021-05-27T09:27:20+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php56",
|
||||
"version": "v1.20.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-php56.git",
|
||||
"reference": "54b8cd7e6c1643d78d011f3be89f3ef1f9f4c675"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/54b8cd7e6c1643d78d011f3be89f3ef1f9f4c675",
|
||||
"reference": "54b8cd7e6c1643d78d011f3be89f3ef1f9f4c675",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1"
|
||||
},
|
||||
"type": "metapackage",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.20-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
"url": "https://github.com/symfony/polyfill"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nicolas Grekas",
|
||||
"email": "p@tchwork.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony polyfill backporting some PHP 5.6+ features to lower PHP versions",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"compatibility",
|
||||
"polyfill",
|
||||
"portable",
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-php56/tree/v1.20.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2020-10-23T14:02:19+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php73",
|
||||
"version": "v1.23.0",
|
||||
|
@ -9942,5 +10385,5 @@
|
|||
"ext-iconv": "*"
|
||||
},
|
||||
"platform-dev": [],
|
||||
"plugin-api-version": "2.0.0"
|
||||
"plugin-api-version": "2.1.0"
|
||||
}
|
||||
|
|
|
@ -12,4 +12,6 @@ return [
|
|||
Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
|
||||
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
|
||||
Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true],
|
||||
Lexik\Bundle\JWTAuthenticationBundle\LexikJWTAuthenticationBundle::class => ['all' => true],
|
||||
SymfonyBundles\JsonRequestBundle\JsonRequestBundle::class => ['all' => true],
|
||||
];
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
lexik_jwt_authentication:
|
||||
secret_key: '%env(resolve:JWT_SECRET_KEY)%'
|
||||
public_key: '%env(resolve:JWT_PUBLIC_KEY)%'
|
||||
pass_phrase: '%env(JWT_PASSPHRASE)%'
|
|
@ -13,17 +13,29 @@ security:
|
|||
app_user_provider:
|
||||
entity:
|
||||
class: App\Entity\User
|
||||
property: id
|
||||
property: email
|
||||
# used to reload user from session & other features (e.g. switch_user)
|
||||
# used to reload user from session & other features (e.g. switch_user)
|
||||
firewalls:
|
||||
login:
|
||||
pattern: ^/api/login
|
||||
stateless: true
|
||||
json_login:
|
||||
check_path: /api/login
|
||||
success_handler: lexik_jwt_authentication.handler.authentication_success
|
||||
failure_handler: lexik_jwt_authentication.handler.authentication_failure
|
||||
api:
|
||||
pattern: ^/api
|
||||
stateless: true
|
||||
guard:
|
||||
authenticators:
|
||||
- lexik_jwt_authentication.jwt_token_authenticator
|
||||
dev:
|
||||
pattern: ^/(_(profiler|wdt)|css|images|js)/
|
||||
security: false
|
||||
main:
|
||||
lazy: true
|
||||
provider: app_user_provider
|
||||
|
||||
# activate different ways to authenticate
|
||||
# https://symfony.com/doc/current/security.html#firewalls-authentication
|
||||
|
||||
|
@ -35,3 +47,6 @@ security:
|
|||
access_control:
|
||||
# - { path: ^/admin, roles: ROLE_ADMIN }
|
||||
# - { path: ^/profile, roles: ROLE_USER }
|
||||
# - { path: ^/register, roles: IS_AUTHENTICATED_ANONYMOUSLY }
|
||||
# - { path: ^/api/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
|
||||
# - { path: ^/api, roles: IS_AUTHENTICATED_FULLY }
|
|
@ -1 +1,3 @@
|
|||
cd ../../docker
|
||||
docker.exe compose -p curenet exec app bash -c 'cd /web/backend && bash'
|
||||
cd src/backend
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\User;
|
||||
use App\Traits\JsonResponseTrait;
|
||||
use App\Repository\UserRepository;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenManagerInterface;
|
||||
|
||||
class AuthController extends AbstractController
|
||||
{
|
||||
use JsonResponseTrait;
|
||||
|
||||
public function __construct(private UserRepository $userRepository) {
|
||||
|
||||
}
|
||||
|
||||
#[Route('/api/register', methods: ["POST"], name: 'register')]
|
||||
public function register(Request $request, UserPasswordHasherInterface $encoder)
|
||||
{
|
||||
$em = $this->getDoctrine()->getManager();
|
||||
$name = $request->get('name');
|
||||
$surname = $request->get('surname');
|
||||
$password = $request->get('password');
|
||||
$email = $request->get('email');
|
||||
|
||||
if (empty($name) || empty($password) || empty($email)){
|
||||
return $this->notAcceptable("Invalid Username or Password or Email");
|
||||
}
|
||||
|
||||
$existing = $this->userRepository->findByEmail($email);
|
||||
if ($existing) {
|
||||
return $this->notAcceptable("User email exists");
|
||||
}
|
||||
|
||||
$user = new User();
|
||||
$user->setPassword($encoder->hashPassword($user, $password));
|
||||
$user->setEmail($email);
|
||||
$user->setName($name);
|
||||
$user->setSurname($surname);
|
||||
$em->persist($user);
|
||||
$em->flush();
|
||||
return $this->created($user);
|
||||
}
|
||||
|
||||
#[Route('/api/login', methods: ['POST'], name: 'login')]
|
||||
public function login(JWTTokenManagerInterface $JWTManager)
|
||||
{
|
||||
$user = new User();
|
||||
return $this->ok(['token' => $JWTManager->create($user)]);
|
||||
}
|
||||
|
||||
#[Route('/api/user-check', methods: ['POST'])]
|
||||
public function checkUser() {
|
||||
$user = $this->getUser();
|
||||
if (!$user) {
|
||||
return $this->unauthorized([]);
|
||||
}
|
||||
$user->generateAvatar();
|
||||
return $this->ok($user);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
namespace App\Entity\Abstraction;
|
||||
|
||||
use ReflectionClass;
|
||||
use Symfony\Component\Serializer\Serializer;
|
||||
use Symfony\Component\Serializer\Encoder\JsonEncoder;
|
||||
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
|
||||
|
||||
class BaseEntity {
|
||||
protected array $hidden = [];
|
||||
protected array $map = [];
|
||||
private array $systemParams = ['hidden', 'map', 'systemParams'];
|
||||
public function toArray() {
|
||||
$output = [];
|
||||
$normalizers = [new ObjectNormalizer()];
|
||||
$serializer = new Serializer($normalizers);
|
||||
$array = $serializer->normalize($this, null);
|
||||
|
||||
$hidden = array_merge($this->hidden, $this->systemParams);
|
||||
foreach ($array as $key => $value) {
|
||||
if (in_array($key, $hidden)) {
|
||||
continue;
|
||||
}
|
||||
if (isset($this->map[$key])) {
|
||||
$key = $this->map[$key];
|
||||
}
|
||||
$output[$key] = $value;
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
}
|
|
@ -2,17 +2,21 @@
|
|||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\UserRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
|
||||
use App\Repository\UserRepository;
|
||||
use App\Entity\Abstraction\BaseEntity;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Lexik\Bundle\JWTAuthenticationBundle\Security\User\JWTUserInterface;
|
||||
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
|
||||
|
||||
/**
|
||||
* @ORM\Entity(repositoryClass=UserRepository::class)
|
||||
* @ORM\Table(name="`user`")
|
||||
*/
|
||||
class User implements UserInterface, PasswordAuthenticatedUserInterface
|
||||
class User extends BaseEntity implements UserInterface, PasswordAuthenticatedUserInterface, JWTUserInterface
|
||||
{
|
||||
protected array $hidden = ['password', 'salt', 'userIdentifier', 'username'];
|
||||
|
||||
/**
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue
|
||||
|
@ -61,6 +65,12 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
|
|||
*/
|
||||
private $state;
|
||||
|
||||
public static function createFromPayload($username, array $payload)
|
||||
{
|
||||
$user = new User();
|
||||
return $user;
|
||||
}
|
||||
|
||||
public function getId(): ?string
|
||||
{
|
||||
return $this->id;
|
||||
|
@ -80,7 +90,7 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
|
|||
*/
|
||||
public function getUserIdentifier(): string
|
||||
{
|
||||
return (string) $this->id;
|
||||
return (string) $this->email;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -88,7 +98,7 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
|
|||
*/
|
||||
public function getUsername(): string
|
||||
{
|
||||
return (string) $this->id;
|
||||
return (string) $this->email;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -216,4 +226,10 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
|
|||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function generateAvatar() {
|
||||
if (!$this->avatar) {
|
||||
$this->avatar = 'https://www.gravatar.com/avatar/' . md5($this->email) . '?s=514&d=robohash';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,6 +36,20 @@ class UserRepository extends ServiceEntityRepository implements PasswordUpgrader
|
|||
$this->_em->flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return User[] Returns an array of User objects
|
||||
*/
|
||||
public function findByEmail($value)
|
||||
{
|
||||
return $this->createQueryBuilder('u')
|
||||
->andWhere('u.email = :val')
|
||||
->setParameter('val', $value)
|
||||
->setMaxResults(1)
|
||||
->getQuery()
|
||||
->getOneOrNullResult()
|
||||
;
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @return User[] Returns an array of User objects
|
||||
// */
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
namespace App\Traits;
|
||||
|
||||
use App\Entity\Abstraction\BaseEntity;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
|
||||
trait JsonResponseTrait {
|
||||
protected function ok($data, $code = Response::HTTP_OK, $headers = []) {
|
||||
return $this->response($data, $code, $headers);
|
||||
}
|
||||
|
||||
protected function created($data, $code = Response::HTTP_CREATED, $headers = []) {
|
||||
return $this->response($data, $code, $headers);
|
||||
}
|
||||
|
||||
protected function notAcceptable($data, $code = Response::HTTP_NOT_ACCEPTABLE, $headers = []) {
|
||||
return $this->response([ 'error' => 'Not acceptable', 'data' => $data], $code, $headers);
|
||||
}
|
||||
|
||||
protected function unauthorized($data, $code = Response::HTTP_UNAUTHORIZED, $headers = []) {
|
||||
return $this->response([ 'error' => 'Unauthorized', 'data' => $data], $code, $headers);
|
||||
}
|
||||
|
||||
protected function response($data, $code = Response::HTTP_OK, $headers = []) {
|
||||
|
||||
if ($data instanceof BaseEntity) {
|
||||
$data = $data->toArray();
|
||||
}
|
||||
|
||||
return new JsonResponse($data, $code, $headers);
|
||||
}
|
||||
}
|
|
@ -91,12 +91,33 @@
|
|||
"laminas/laminas-code": {
|
||||
"version": "4.4.2"
|
||||
},
|
||||
"lcobucci/clock": {
|
||||
"version": "2.0.0"
|
||||
},
|
||||
"lcobucci/jwt": {
|
||||
"version": "4.1.4"
|
||||
},
|
||||
"lexik/jwt-authentication-bundle": {
|
||||
"version": "2.5",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "master",
|
||||
"version": "2.5",
|
||||
"ref": "5b2157bcd5778166a5696e42f552ad36529a07a6"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/lexik_jwt_authentication.yaml"
|
||||
]
|
||||
},
|
||||
"monolog/monolog": {
|
||||
"version": "2.3.1"
|
||||
},
|
||||
"myclabs/deep-copy": {
|
||||
"version": "1.10.2"
|
||||
},
|
||||
"namshi/jose": {
|
||||
"version": "7.2.3"
|
||||
},
|
||||
"nikic/php-parser": {
|
||||
"version": "v4.12.0"
|
||||
},
|
||||
|
@ -222,6 +243,9 @@
|
|||
"config/packages/sensio_framework_extra.yaml"
|
||||
]
|
||||
},
|
||||
"symfony-bundles/json-request-bundle": {
|
||||
"version": "4.0.5"
|
||||
},
|
||||
"symfony/asset": {
|
||||
"version": "v5.3.2"
|
||||
},
|
||||
|
@ -442,6 +466,9 @@
|
|||
"symfony/polyfill-mbstring": {
|
||||
"version": "v1.23.0"
|
||||
},
|
||||
"symfony/polyfill-php56": {
|
||||
"version": "v1.20.0"
|
||||
},
|
||||
"symfony/polyfill-php73": {
|
||||
"version": "v1.23.0"
|
||||
},
|
||||
|
|
|
@ -1715,6 +1715,14 @@
|
|||
"schema-utils": "^2.7.0"
|
||||
}
|
||||
},
|
||||
"@ng-stack/forms": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@ng-stack/forms/-/forms-2.4.0.tgz",
|
||||
"integrity": "sha512-Czo4ZbzEcTSAQ8yS4d0PzYMeB8GidOz1ABZhUjwnZiLaNOY2gnIdpQEIqE+GjIVOyusGEjQ/psLneBj8gE2EjQ==",
|
||||
"requires": {
|
||||
"tslib": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"@ngtools/webpack": {
|
||||
"version": "12.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-12.1.3.tgz",
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
"@angular/platform-browser": "~12.1.3",
|
||||
"@angular/platform-browser-dynamic": "~12.1.3",
|
||||
"@angular/router": "~12.1.3",
|
||||
"@ng-stack/forms": "^2.4.0",
|
||||
"@ngx-translate/core": "^13.0.0",
|
||||
"@ngx-translate/http-loader": "^6.0.0",
|
||||
"bootstrap": "^5.0.2",
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { Route, RouterModule } from '@angular/router';
|
||||
import { AuthGuard } from './modules/auth/services/auth/auth.guard';
|
||||
import { NotFoundComponent } from './modules/shared/components/not-found/not-found.component';
|
||||
|
||||
interface AppMenuEntry {
|
||||
|
@ -15,6 +16,10 @@ interface AppRoute extends Route {
|
|||
}
|
||||
|
||||
export const appRoutes: AppRoute[] = [
|
||||
{
|
||||
path: 'auth',
|
||||
loadChildren: () => import('./modules/auth/auth.module').then(m => m.AuthModule),
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
component: NotFoundComponent,
|
||||
|
@ -25,6 +30,8 @@ export const appRoutes: AppRoute[] = [
|
|||
matchExact: true,
|
||||
},
|
||||
],
|
||||
canActivate: [ AuthGuard, ],
|
||||
canActivateChild: [ AuthGuard, ],
|
||||
},
|
||||
{
|
||||
path: 'profile',
|
||||
|
@ -35,6 +42,8 @@ export const appRoutes: AppRoute[] = [
|
|||
icon: 'fa fa-user',
|
||||
},
|
||||
],
|
||||
canActivate: [ AuthGuard, ],
|
||||
canActivateChild: [ AuthGuard, ],
|
||||
},
|
||||
{
|
||||
path: 'community',
|
||||
|
@ -45,6 +54,21 @@ export const appRoutes: AppRoute[] = [
|
|||
icon: 'fa fa-users',
|
||||
},
|
||||
],
|
||||
canActivate: [ AuthGuard, ],
|
||||
canActivateChild: [ AuthGuard, ],
|
||||
},
|
||||
{
|
||||
path: 'questions',
|
||||
component: NotFoundComponent,
|
||||
menuEntries: [
|
||||
{
|
||||
label: 'MENU.QUESTIONS',
|
||||
icon: 'fa fa-question',
|
||||
level: 1,
|
||||
}
|
||||
],
|
||||
canActivate: [ AuthGuard, ],
|
||||
canActivateChild: [ AuthGuard, ],
|
||||
},
|
||||
{
|
||||
path: 'messages',
|
||||
|
@ -55,6 +79,8 @@ export const appRoutes: AppRoute[] = [
|
|||
icon: 'fa fa-comments',
|
||||
},
|
||||
],
|
||||
canActivate: [ AuthGuard, ],
|
||||
canActivateChild: [ AuthGuard, ],
|
||||
},
|
||||
{
|
||||
path: 'tests',
|
||||
|
@ -65,6 +91,8 @@ export const appRoutes: AppRoute[] = [
|
|||
icon: 'fa fa-heartbeat',
|
||||
},
|
||||
],
|
||||
canActivate: [ AuthGuard, ],
|
||||
canActivateChild: [ AuthGuard, ],
|
||||
},
|
||||
{
|
||||
path: 'research',
|
||||
|
@ -75,6 +103,8 @@ export const appRoutes: AppRoute[] = [
|
|||
icon: 'fa fa-flask',
|
||||
},
|
||||
],
|
||||
canActivate: [ AuthGuard, ],
|
||||
canActivateChild: [ AuthGuard, ],
|
||||
},
|
||||
{
|
||||
path: 'admin',
|
||||
|
@ -92,6 +122,8 @@ export const appRoutes: AppRoute[] = [
|
|||
level: 1,
|
||||
}
|
||||
],
|
||||
canActivate: [ AuthGuard, ],
|
||||
canActivateChild: [ AuthGuard, ],
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
@ -1,18 +1,23 @@
|
|||
|
||||
<ng-template #toolbar>
|
||||
<mat-toolbar [color]="isDark ? null : 'primary'">
|
||||
<button mat-icon-button (click)="toggleSidebar()">
|
||||
<mat-toolbar class="toolbar-background">
|
||||
<button mat-icon-button (click)="toggleSidebar()" *ngIf="!isSidebarHidden">
|
||||
<i class="fa fa-bars"></i>
|
||||
</button>
|
||||
<span>{{ 'APP.NAME' | translate }}</span>
|
||||
<span class="flex-grow-1"></span>
|
||||
<div>
|
||||
<div *ngFor="let component of dynamicToolbarComponents">
|
||||
<ng-container [ngComponentOutlet]="component"></ng-container>
|
||||
</div>
|
||||
</div>
|
||||
</mat-toolbar>
|
||||
</ng-template>
|
||||
<ng-container *ngIf="!isMobile">
|
||||
<ng-container *ngTemplateOutlet="toolbar"></ng-container>
|
||||
</ng-container>
|
||||
<mat-drawer-container class="flex-grow-1">
|
||||
<mat-drawer [mode]="sidebarMode" [opened]="isSidebarOpen" (openedChange)="onSidebarOpenedChange($event)">
|
||||
<mat-drawer [mode]="sidebarMode" [opened]="isSidebarOpen" (openedChange)="onSidebarOpenedChange($event)" *ngIf="!isSidebarHidden">
|
||||
<div class="menu">
|
||||
<div>
|
||||
<ng-container *ngFor="let route of appRoutes">
|
||||
|
@ -29,10 +34,9 @@
|
|||
</div>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
|
||||
</div>
|
||||
<div>
|
||||
<div class="d-flex user-box">
|
||||
<div class="d-flex user-box" *ngIf="user">
|
||||
<div class="avatar" [style.background-image]="url(user.avatar)"></div>
|
||||
<div class="d-flex align-items-center flex-grow-1 p-2">{{ user.name }} {{ user.surname }}</div>
|
||||
<div class="user-menu-container">
|
||||
|
@ -55,10 +59,12 @@
|
|||
<mat-option *ngFor="let theme of themes" [value]="theme">{{ 'THEME.'+theme.toUpperCase() | translate }}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<button mat-menu-item (click)="logout()">
|
||||
Wyloguj
|
||||
</button>
|
||||
</mat-menu>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</mat-drawer>
|
||||
|
|
|
@ -5,12 +5,16 @@ import { AppService, WindowSize } from './modules/shared/services/app/app.servic
|
|||
import { BrowserStorageService } from './modules/shared/services/browser-storage/browser-storage.service';
|
||||
import { ThemeService } from './modules/shared/services/theme/theme.service';
|
||||
import { DomSanitizer, SafeStyle } from '@angular/platform-browser';
|
||||
import { AuthService } from './modules/auth/services/auth/auth.service';
|
||||
import { Router } from '@angular/router';
|
||||
import { UserModel } from './modules/auth/models/user.model';
|
||||
|
||||
enum SidebarOpenEnum {
|
||||
Default,
|
||||
Open,
|
||||
Closed,
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
|
@ -28,74 +32,100 @@ export class AppComponent {
|
|||
themes: string[] = [];
|
||||
themeControl = new FormControl();
|
||||
langControl = new FormControl();
|
||||
isDark = true;
|
||||
appRoutes = appRoutes;
|
||||
user = {
|
||||
name: 'Name',
|
||||
surname: 'Surname',
|
||||
avatar: 'https://www.gravatar.com/avatar/81b206a89f89d5b1123b87606075c6a8?s=514&d=robohash',
|
||||
};
|
||||
user: UserModel;
|
||||
isSidebarHidden = false;
|
||||
dynamicToolbarComponents = [];
|
||||
|
||||
get isSidebarOpen() {
|
||||
get isSidebarOpen(): boolean {
|
||||
switch (this.sidebarOpen) {
|
||||
case SidebarOpenEnum.Open:
|
||||
return true;
|
||||
break;
|
||||
case SidebarOpenEnum.Closed:
|
||||
return false;
|
||||
break;
|
||||
default:
|
||||
return this.defaultSidebarOpen;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(
|
||||
appService: AppService,
|
||||
themeService: ThemeService,
|
||||
private appService: AppService,
|
||||
private themeService: ThemeService,
|
||||
private browserStorageService: BrowserStorageService,
|
||||
private sanitizer: DomSanitizer,
|
||||
private authService: AuthService,
|
||||
private router: Router,
|
||||
) {
|
||||
this.themes = themeService.getThemes();
|
||||
this.langs = appService.getLangs();
|
||||
this.lang = appService.getLang();
|
||||
this.configureSidebarEvents();
|
||||
this.configureThemeEvents();
|
||||
this.configureLanguageEvents();
|
||||
this.configureResizeEvents();
|
||||
this.configureUserEvents();
|
||||
}
|
||||
|
||||
this.themeControl.valueChanges.subscribe(theme => {
|
||||
themeService.setTheme(theme);
|
||||
});
|
||||
appService.changeLang(this.lang);
|
||||
this.langControl.valueChanges.subscribe(lang => {
|
||||
appService.changeLang(lang);
|
||||
});
|
||||
this.onThemeChanged(themeService.getTheme());
|
||||
themeService.themeChange.subscribe(theme => {
|
||||
this.onThemeChanged(theme);
|
||||
});
|
||||
this.onResize(appService.size);
|
||||
appService.sizeChange.subscribe(size => {
|
||||
this.onResize(size);
|
||||
});
|
||||
this.onLangChanged(appService.getLang());
|
||||
appService.langChange.subscribe(lang => {
|
||||
this.onLangChanged(lang);
|
||||
configureUserEvents(): void {
|
||||
this.user = this.authService.getUser();
|
||||
this.authService.userChange.subscribe(user => {
|
||||
this.user = user;
|
||||
});
|
||||
}
|
||||
|
||||
configureSidebarEvents(): void {
|
||||
this.isSidebarHidden = this.appService.getSidebarHidden();
|
||||
this.appService.sidebarHiddenChange.subscribe(hidden => {
|
||||
this.isSidebarHidden = hidden;
|
||||
});
|
||||
const sidebarUserPrefference = this.browserStorageService.getItem('sidebar.open');
|
||||
if (sidebarUserPrefference !== null) {
|
||||
this.sidebarOpen = sidebarUserPrefference;
|
||||
}
|
||||
this.appService.dynamicToolbarComponentsChange.subscribe(components => {
|
||||
this.dynamicToolbarComponents = components;
|
||||
});
|
||||
}
|
||||
|
||||
onThemeChanged(theme: string) {
|
||||
configureThemeEvents(): void {
|
||||
this.themes = this.themeService.getThemes();
|
||||
this.themeControl.valueChanges.subscribe(theme => {
|
||||
this.themeService.setTheme(theme);
|
||||
});
|
||||
this.onThemeChanged(this.themeService.getTheme());
|
||||
this.themeService.themeChange.subscribe(theme => {
|
||||
this.onThemeChanged(theme);
|
||||
});
|
||||
}
|
||||
|
||||
configureLanguageEvents(): void {
|
||||
this.langs = this.appService.getLangs();
|
||||
this.lang = this.appService.getLang();
|
||||
|
||||
this.appService.changeLang(this.lang);
|
||||
this.langControl.valueChanges.subscribe(lang => {
|
||||
this.appService.changeLang(lang);
|
||||
});
|
||||
|
||||
this.onLangChanged(this.appService.getLang());
|
||||
this.appService.langChange.subscribe(lang => {
|
||||
this.onLangChanged(lang);
|
||||
});
|
||||
}
|
||||
|
||||
configureResizeEvents(): void {
|
||||
this.onResize(this.appService.size);
|
||||
this.appService.sizeChange.subscribe(size => {
|
||||
this.onResize(size);
|
||||
});
|
||||
}
|
||||
|
||||
onThemeChanged(theme: string): void {
|
||||
if (theme === undefined) {
|
||||
return;
|
||||
}
|
||||
this.theme = theme;
|
||||
this.themeControl.setValue(theme);
|
||||
this.isDark = theme.indexOf('dark') !== -1;
|
||||
}
|
||||
|
||||
onResize(size: WindowSize) {
|
||||
onResize(size: WindowSize): void {
|
||||
if (size === undefined) {
|
||||
return;
|
||||
}
|
||||
|
@ -104,7 +134,7 @@ export class AppComponent {
|
|||
this.sidebarMode = this.isMobile ? 'over' : 'side';
|
||||
}
|
||||
|
||||
onLangChanged(lang: string) {
|
||||
onLangChanged(lang: string): void {
|
||||
if (lang === undefined) {
|
||||
return;
|
||||
}
|
||||
|
@ -112,16 +142,21 @@ export class AppComponent {
|
|||
this.langControl.setValue(lang);
|
||||
}
|
||||
|
||||
toggleSidebar() {
|
||||
toggleSidebar(): void {
|
||||
this.sidebarOpen = this.isSidebarOpen ? SidebarOpenEnum.Closed : SidebarOpenEnum.Open;
|
||||
this.browserStorageService.setItem('sidebar.open', this.sidebarOpen);
|
||||
}
|
||||
|
||||
onSidebarOpenedChange(opened: boolean) {
|
||||
onSidebarOpenedChange(opened: boolean): void {
|
||||
this.sidebarOpen = opened ? SidebarOpenEnum.Open : SidebarOpenEnum.Closed;
|
||||
}
|
||||
|
||||
url(url: string): SafeStyle {
|
||||
return this.sanitizer.bypassSecurityTrustStyle(`url('${url}')`);
|
||||
}
|
||||
|
||||
logout(): void {
|
||||
this.authService.logout();
|
||||
this.router.navigateByUrl('/');
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { HttpClientModule, HttpClient } from '@angular/common/http';
|
||||
import { HttpClientModule, HttpClient, HTTP_INTERCEPTORS } from '@angular/common/http';
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
import { AppComponent } from './app.component';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
|
@ -11,8 +11,11 @@ import { BrowserStorageService } from './modules/shared/services/browser-storage
|
|||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
|
||||
import { TokenInterceptor } from './modules/shared/interceptors/token/token.interceptor';
|
||||
import { AuthTokenService } from './modules/shared/services/auth/auth-token.service';
|
||||
import { AuthService } from './modules/auth/services/auth/auth.service';
|
||||
|
||||
export function HttpLoaderFactory(http: HttpClient) {
|
||||
export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader {
|
||||
return new TranslateHttpLoader(http, '/assets/lang/', '.json');
|
||||
}
|
||||
|
||||
|
@ -41,6 +44,9 @@ export function HttpLoaderFactory(http: HttpClient) {
|
|||
AppService,
|
||||
ThemeService,
|
||||
BrowserStorageService,
|
||||
AuthTokenService,
|
||||
AuthService,
|
||||
{ provide: HTTP_INTERCEPTORS, useClass: TokenInterceptor, multi: true }
|
||||
],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Route } from '@angular/router';
|
||||
import { NotFoundComponent } from '../shared/components/not-found/not-found.component';
|
||||
import { UsersComponent } from './components/users/users.component';
|
||||
|
||||
const routes: Route[] = [
|
||||
{
|
||||
|
@ -9,7 +10,7 @@ const routes: Route[] = [
|
|||
},
|
||||
{
|
||||
path: 'users',
|
||||
component: NotFoundComponent,
|
||||
component: UsersComponent,
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
@ -3,14 +3,21 @@ import { CommonModule } from '@angular/common';
|
|||
|
||||
import { AdminRoutingModule } from './admin-routing.module';
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { UsersComponent } from './components/users/users.component';
|
||||
import { AdminUserService } from './services/admin-user/admin-user.service';
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [],
|
||||
declarations: [
|
||||
UsersComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
AdminRoutingModule,
|
||||
SharedModule,
|
||||
]
|
||||
],
|
||||
providers: [
|
||||
AdminUserService,
|
||||
],
|
||||
})
|
||||
export class AdminModule { }
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<p>users works!</p>
|
|
@ -0,0 +1,18 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { AdminUserService } from '../../services/admin-user/admin-user.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-users',
|
||||
templateUrl: './users.component.html',
|
||||
styleUrls: ['./users.component.scss']
|
||||
})
|
||||
export class UsersComponent implements OnInit {
|
||||
|
||||
constructor(private adminUserService: AdminUserService) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.adminUserService.getUsers().subscribe(() => {
|
||||
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
import { HttpClient } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { UserModel } from 'src/app/modules/auth/models/user.model';
|
||||
|
||||
@Injectable()
|
||||
export class AdminUserService {
|
||||
|
||||
constructor(private http: HttpClient) { }
|
||||
|
||||
getUsers(): Observable<UserModel[]> {
|
||||
return this.http.get<UserModel[]>('/api/admin/users');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Route } from '@angular/router';
|
||||
import { AuthComponent } from './components/auth/auth.component';
|
||||
import { AuthTabEnum } from './enums/auth-tab.enum';
|
||||
|
||||
const routes: Route[] = [
|
||||
{
|
||||
path: AuthTabEnum.Login,
|
||||
component: AuthComponent,
|
||||
},
|
||||
{
|
||||
path: AuthTabEnum.Register,
|
||||
component: AuthComponent,
|
||||
},
|
||||
{
|
||||
path: AuthTabEnum.RestorePassword,
|
||||
component: AuthComponent,
|
||||
},
|
||||
|
||||
{
|
||||
path: '**',
|
||||
redirectTo: 'login',
|
||||
},
|
||||
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class AuthRoutingModule { }
|
|
@ -0,0 +1,29 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { AuthRoutingModule } from './auth-routing.module';
|
||||
import { AuthComponent } from './components/auth/auth.component';
|
||||
import { ThemeSwitcherComponent } from './components/theme-switcher/theme-switcher.component';
|
||||
import { MaterialModule } from '../material/material.module';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { AuthService } from './services/auth/auth.service';
|
||||
import { NgStackFormsModule } from '@ng-stack/forms';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AuthComponent,
|
||||
ThemeSwitcherComponent,
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
AuthRoutingModule,
|
||||
MaterialModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
NgStackFormsModule,
|
||||
],
|
||||
providers: [
|
||||
AuthService,
|
||||
],
|
||||
})
|
||||
export class AuthModule { }
|
|
@ -0,0 +1,66 @@
|
|||
<div class="primary-background toolbar-background"></div>
|
||||
<div class="col-11 col-lg-7 col-xl-5 p-3 content">
|
||||
<mat-card class="px-0">
|
||||
|
||||
<mat-tab-group mat-align-tabs="center" [selectedIndex]="selectedIndex" (selectedIndexChange)="selectedIndexChange($event)">
|
||||
<mat-tab label="Logowanie">
|
||||
<div [formGroup]="loginForm" class="row align-items-center p-4 m-0">
|
||||
<div class="col-12 col-md-4" required>Email</div>
|
||||
<mat-form-field class="col-12 col-md-8">
|
||||
<mat-label>Adres email</mat-label>
|
||||
<input formControlName="email" matInput>
|
||||
</mat-form-field>
|
||||
<div class="col-12 col-md-4" required>Hasło</div>
|
||||
<mat-form-field class="col-12 col-md-8">
|
||||
<mat-label>Hasło</mat-label>
|
||||
<input formControlName="password" type="password" matInput>
|
||||
</mat-form-field>
|
||||
<div class="text-end">
|
||||
<button mat-button (click)="selectedIndexChange(getSelectedIndex(AuthTabEnum.RestorePassword))">Odzyskaj hasło</button>
|
||||
<button mat-flat-button color="primary" (click)="login()">Zaloguj</button>
|
||||
</div>
|
||||
</div>
|
||||
</mat-tab>
|
||||
<mat-tab label="Rejestracja">
|
||||
<div [formGroup]="registerForm" class="row align-items-center p-4 m-0">
|
||||
<div class="col-12 col-md-4" required>Imię</div>
|
||||
<mat-form-field class="col-12 col-md-8">
|
||||
<mat-label>Imię</mat-label>
|
||||
<input formControlName="name" matInput>
|
||||
</mat-form-field>
|
||||
<div class="col-12 col-md-4">Nazwisko</div>
|
||||
<mat-form-field class="col-12 col-md-8">
|
||||
<mat-label>Nazwisko</mat-label>
|
||||
<input formControlName="surname" matInput>
|
||||
</mat-form-field>
|
||||
<div class="col-12 col-md-4" required>Email</div>
|
||||
<mat-form-field class="col-12 col-md-8">
|
||||
<mat-label>Adres email</mat-label>
|
||||
<input formControlName="email" matInput>
|
||||
</mat-form-field>
|
||||
<div class="col-12 col-md-4" required>Hasło</div>
|
||||
<mat-form-field class="col-12 col-md-8">
|
||||
<mat-label>Hasło</mat-label>
|
||||
<input formControlName="password" type="password" matInput>
|
||||
</mat-form-field>
|
||||
<div class="text-end">
|
||||
<button mat-flat-button color="primary" (click)="register()">Zarejestruj</button>
|
||||
</div>
|
||||
</div>
|
||||
</mat-tab>
|
||||
<mat-tab label="Odzyskiwanie hasła" *ngIf="selectedIndex === 2">
|
||||
<div [formGroup]="restoreForm" class="row align-items-center p-4 m-0">
|
||||
<div class="col-12 col-md-4" required>Email</div>
|
||||
<mat-form-field class="col-12 col-md-8">
|
||||
<mat-label>Adres email</mat-label>
|
||||
<input formControlName="email" matInput>
|
||||
</mat-form-field>
|
||||
<div class="text-end">
|
||||
<button mat-button (click)="selectedIndexChange(getSelectedIndex(AuthTabEnum.Login))">Anuluj</button>
|
||||
<button mat-flat-button color="primary">Odzyskaj hasło</button>
|
||||
</div>
|
||||
</div>
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
</mat-card>
|
||||
</div>
|
|
@ -0,0 +1,9 @@
|
|||
.primary-background{
|
||||
background-color: var(--toolbar-background);
|
||||
height: 200px;
|
||||
}
|
||||
.content{
|
||||
margin: 0px auto;
|
||||
margin-top: -150px;
|
||||
height: 200px;
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
import { Location } from '@angular/common';
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute, Router, UrlSegment } from '@angular/router';
|
||||
import { FormBuilder, FormGroup } from '@ng-stack/forms';
|
||||
import { AppService } from 'src/app/modules/shared/services/app/app.service';
|
||||
import { AuthTabEnum } from '../../enums/auth-tab.enum';
|
||||
import { LoginModel } from '../../models/login.model';
|
||||
import { RegisterModel } from '../../models/register.model';
|
||||
import { RestoreModel } from '../../models/restore.model';
|
||||
import { AuthService } from '../../services/auth/auth.service';
|
||||
import { ThemeSwitcherComponent } from '../theme-switcher/theme-switcher.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-auth',
|
||||
templateUrl: './auth.component.html',
|
||||
styleUrls: ['./auth.component.scss']
|
||||
})
|
||||
export class AuthComponent implements OnInit, OnDestroy {
|
||||
|
||||
registerForm: FormGroup<RegisterModel>;
|
||||
loginForm: FormGroup<LoginModel>;
|
||||
restoreForm: FormGroup<RestoreModel>;
|
||||
tab: AuthTabEnum;
|
||||
selectedIndex: number;
|
||||
defaultTab = AuthTabEnum.Login;
|
||||
tabIndexes = [
|
||||
AuthTabEnum.Login,
|
||||
AuthTabEnum.Register,
|
||||
AuthTabEnum.RestorePassword,
|
||||
];
|
||||
showRestore = false;
|
||||
AuthTabEnum = AuthTabEnum;
|
||||
|
||||
constructor(
|
||||
private appService: AppService,
|
||||
private authService: AuthService,
|
||||
private activatedRoute: ActivatedRoute,
|
||||
private router: Router,
|
||||
private location: Location,
|
||||
formBuilder: FormBuilder,
|
||||
) {
|
||||
this.registerForm = formBuilder.group<RegisterModel>({
|
||||
name: 'Name',
|
||||
surname: 'Surname',
|
||||
email: 'email@test.com',
|
||||
password: 'password',
|
||||
});
|
||||
this.loginForm = formBuilder.group<LoginModel>({
|
||||
email: 'email@test.com',
|
||||
password: 'password',
|
||||
});
|
||||
this.restoreForm = formBuilder.group<RestoreModel>({
|
||||
email: 'email@test.com',
|
||||
});
|
||||
|
||||
this.handleUrl(activatedRoute.snapshot.url);
|
||||
activatedRoute.url.subscribe(url => {
|
||||
this.handleUrl(url);
|
||||
});
|
||||
}
|
||||
|
||||
handleUrl(url: UrlSegment[]): void {
|
||||
const path = url[0].path;
|
||||
this.tab = path as AuthTabEnum;
|
||||
this.selectedIndex = this.getSelectedIndex();
|
||||
}
|
||||
|
||||
getSelectedIndex(tab: AuthTabEnum = null): number {
|
||||
if (tab === null) {
|
||||
tab = this.tab;
|
||||
}
|
||||
let index = this.tabIndexes.findIndex(i => i === tab);
|
||||
if (index === -1) {
|
||||
index = this.tabIndexes.findIndex(i => i === this.defaultTab);
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
getIndexTab(): AuthTabEnum {
|
||||
return this.tabIndexes[this.selectedIndex];
|
||||
}
|
||||
|
||||
selectedIndexChange(index: number): void {
|
||||
this.selectedIndex = index;
|
||||
const tab = this.getIndexTab();
|
||||
const url = this.router.createUrlTree(['..', tab], {
|
||||
relativeTo: this.activatedRoute,
|
||||
}).toString();
|
||||
this.location.go(url);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.appService.setSidebarHidden(true);
|
||||
this.appService.addDynamicToolbarComponent(ThemeSwitcherComponent);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.appService.setSidebarHidden(false);
|
||||
this.appService.removeDynamicToolbarComponent(ThemeSwitcherComponent);
|
||||
}
|
||||
|
||||
register(): void {
|
||||
this.authService.createAccount(this.registerForm.getRawValue()).subscribe(data => {
|
||||
this.loginForm.patchValue({
|
||||
email: this.registerForm.controls.email.value,
|
||||
password: this.registerForm.controls.password.value,
|
||||
});
|
||||
this.selectedIndexChange(this.getSelectedIndex(AuthTabEnum.Login));
|
||||
});
|
||||
}
|
||||
|
||||
login(): void {
|
||||
this.authService.login(this.loginForm.getRawValue()).subscribe(data => {
|
||||
const afterUrl = '/';
|
||||
this.router.navigateByUrl(afterUrl);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
<button mat-icon-button (click)="toggleTheme()">
|
||||
<i class="fa" [class.fa-sun-o]="isDark" [class.fa-moon-o]="!isDark"></i>
|
||||
</button>
|
|
@ -0,0 +1,32 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { ThemeService } from 'src/app/modules/shared/services/theme/theme.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-theme-switcher',
|
||||
templateUrl: './theme-switcher.component.html',
|
||||
styleUrls: ['./theme-switcher.component.scss']
|
||||
})
|
||||
export class ThemeSwitcherComponent implements OnInit {
|
||||
|
||||
isDark = false;
|
||||
darkTheme = 'dark';
|
||||
lightTheme = 'light';
|
||||
theme = 'light';
|
||||
|
||||
constructor(
|
||||
private themeService: ThemeService,
|
||||
) {
|
||||
this.theme = themeService.getTheme();
|
||||
themeService.themeChange.subscribe(theme => {
|
||||
this.theme = theme;
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
toggleTheme(): void {
|
||||
this.themeService.setTheme(this.theme === this.darkTheme ? this.lightTheme : this.darkTheme);
|
||||
this.isDark = this.theme === this.darkTheme;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
export enum AuthTabEnum {
|
||||
Login = 'login',
|
||||
Register = 'register',
|
||||
RestorePassword = 'restore-password',
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
export interface LoginModel {
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
export interface RegisterModel {
|
||||
name: string;
|
||||
surname: string;
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
export interface RestoreModel {
|
||||
email: string;
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
export interface TokenResponse {
|
||||
token: string;
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
export interface UserModel {
|
||||
id: number;
|
||||
name: string;
|
||||
surname: string;
|
||||
avatar: string | null;
|
||||
country: string | null;
|
||||
state: string | null;
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, CanActivate, CanActivateChild, Router, RouterStateSnapshot, UrlTree } from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
import { AuthTokenService } from 'src/app/modules/shared/services/auth/auth-token.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class AuthGuard implements CanActivate, CanActivateChild {
|
||||
constructor(private authTokenService: AuthTokenService, private router: Router) {
|
||||
|
||||
}
|
||||
canActivate(
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
|
||||
const can = this.authTokenService.userValidate();
|
||||
if (!can){
|
||||
this.router.navigate(['/auth']);
|
||||
}
|
||||
return can;
|
||||
}
|
||||
|
||||
canActivateChild(
|
||||
childRoute: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
|
||||
const can = this.authTokenService.userValidate();
|
||||
if (!can){
|
||||
this.router.navigate(['/auth']);
|
||||
}
|
||||
return can;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
import { HttpClient } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable, Subject } from 'rxjs';
|
||||
import { LoginModel } from '../../models/login.model';
|
||||
import { RegisterModel } from '../../models/register.model';
|
||||
import { TokenResponse } from '../../models/token.response';
|
||||
import { UserModel } from '../../models/user.model';
|
||||
import { AuthTokenService } from '../../../shared/services/auth/auth-token.service';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
|
||||
private static updateAgent: any;
|
||||
private user: UserModel;
|
||||
public userChange = new Subject<UserModel>();
|
||||
|
||||
constructor(
|
||||
private http: HttpClient,
|
||||
private authTokenService: AuthTokenService,
|
||||
private router: Router,
|
||||
) {
|
||||
this.checkUser();
|
||||
this.userChange.subscribe(user => {
|
||||
this.user = user;
|
||||
});
|
||||
}
|
||||
|
||||
getUser(): UserModel {
|
||||
return this.user;
|
||||
}
|
||||
|
||||
checkUser(): void {
|
||||
this.http.post<UserModel>('/api/user-check', {}).subscribe(user => {
|
||||
this.userChange.next(user);
|
||||
if (AuthService.updateAgent) {
|
||||
clearTimeout(AuthService.updateAgent);
|
||||
}
|
||||
AuthService.updateAgent = setTimeout(() => {
|
||||
this.checkUser();
|
||||
}, 2000) as any;
|
||||
}, () => {
|
||||
this.logout();
|
||||
});
|
||||
}
|
||||
|
||||
createAccount(data: RegisterModel): Observable<UserModel> {
|
||||
return this.http.post<UserModel>('/api/register', data);
|
||||
}
|
||||
|
||||
login(data: LoginModel): Observable<TokenResponse> {
|
||||
const out = new Subject<TokenResponse>();
|
||||
this.http.post<TokenResponse>('/api/login', {
|
||||
username: data.email,
|
||||
password: data.password,
|
||||
}).subscribe(response => {
|
||||
this.authTokenService.setToken(response.token);
|
||||
this.checkUser();
|
||||
out.next(response);
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
||||
logout(): void {
|
||||
this.authTokenService.removeToken();
|
||||
this.router.navigate(['/auth']);
|
||||
}
|
||||
}
|
|
@ -5,6 +5,9 @@ import {MatButtonModule} from '@angular/material/button';
|
|||
import {MatSidenavModule} from '@angular/material/sidenav';
|
||||
import {MatSelectModule} from '@angular/material/select';
|
||||
import {MatMenuModule} from '@angular/material/menu';
|
||||
import {MatCardModule} from '@angular/material/card';
|
||||
import {MatTabsModule} from '@angular/material/tabs';
|
||||
import {MatInputModule} from '@angular/material/input';
|
||||
|
||||
const itemsToExport = [
|
||||
MatToolbarModule,
|
||||
|
@ -12,6 +15,9 @@ const itemsToExport = [
|
|||
MatSidenavModule,
|
||||
MatSelectModule,
|
||||
MatMenuModule,
|
||||
MatCardModule,
|
||||
MatTabsModule,
|
||||
MatInputModule,
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
|
|
@ -1,3 +1,10 @@
|
|||
<div class="m-3 p-3">
|
||||
<div class="primary-background toolbar-background"></div>
|
||||
<div class="content align-items-center justify-content-center">
|
||||
<div class="col-11 col-lg-7 col-xl-5">
|
||||
<mat-card>
|
||||
<div class="m-3 p-3">
|
||||
{{ 'ERROR.PAGE_NOT_FOUND' | translate }}
|
||||
</div>
|
||||
</mat-card>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,10 @@
|
|||
.primary-background{
|
||||
background-color: var(--toolbar-background);
|
||||
height: 100px;
|
||||
}
|
||||
.content{
|
||||
margin: 0px auto;
|
||||
margin-top: -100px;
|
||||
height: 200px;
|
||||
display: flex;
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { AuthTokenService } from 'src/app/modules/shared/services/auth/auth-token.service';
|
||||
|
||||
@Injectable()
|
||||
export class TokenInterceptor implements HttpInterceptor {
|
||||
|
||||
constructor(private authTokenService: AuthTokenService) {
|
||||
}
|
||||
|
||||
intercept(httpRequest: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||
if (this.authTokenService.hasToken()) {
|
||||
const Authorization = `Bearer ${this.authTokenService.getToken()}`;
|
||||
return next.handle(httpRequest.clone({ setHeaders: { Authorization } }));
|
||||
}
|
||||
return next.handle(httpRequest);
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
export class User {
|
||||
id: number;
|
||||
email: string;
|
||||
name: string;
|
||||
surname: string;
|
||||
avatar: string;
|
||||
country: string;
|
||||
state: string;
|
||||
}
|
|
@ -13,57 +13,95 @@ export class WindowSize {
|
|||
this.isMobile = this.width <= 700;
|
||||
}
|
||||
|
||||
update() {
|
||||
this.width = window.innerWidth;
|
||||
this.height = window.innerHeight;
|
||||
static generate(): WindowSize {
|
||||
return new WindowSize(window.innerWidth, window.innerHeight);
|
||||
}
|
||||
|
||||
static generate() {
|
||||
return new WindowSize(window.innerWidth, window.innerHeight);
|
||||
update(): WindowSize {
|
||||
this.width = window.innerWidth;
|
||||
this.height = window.innerHeight;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class AppService {
|
||||
private sidebarHidden = false;
|
||||
private lang = 'en';
|
||||
private defaultLang = 'en';
|
||||
private dynamicToolbarComponents = [];
|
||||
langChange = new Subject<string>();
|
||||
size: WindowSize;
|
||||
sizeChange = new Subject<WindowSize>();
|
||||
sidebarHiddenChange = new Subject<boolean>();
|
||||
dynamicToolbarComponentsChange = new Subject<any[]>();
|
||||
|
||||
constructor(
|
||||
browserStorageService: BrowserStorageService,
|
||||
private browserStorageService: BrowserStorageService,
|
||||
private translate: TranslateService,
|
||||
) {
|
||||
this.langChange.subscribe(lang => {
|
||||
this.lang = lang;
|
||||
browserStorageService.setItem('language', lang);
|
||||
});
|
||||
this.sizeChange.subscribe(size => this.size = size);
|
||||
this.sizeChange.next(WindowSize.generate());
|
||||
let lang = browserStorageService.getItem('language');
|
||||
if (!lang) {
|
||||
lang = this.defaultLang;
|
||||
}
|
||||
this.translate.setDefaultLang(this.defaultLang);
|
||||
this.changeLang(lang);
|
||||
fromEvent(window, 'resize').subscribe(e => {
|
||||
this.sizeChange.next(WindowSize.generate());
|
||||
})
|
||||
this.configureResizeEvents();
|
||||
this.configureLanguageEvents();
|
||||
this.configureSidebarEvents();
|
||||
}
|
||||
|
||||
changeLang(lang: string) {
|
||||
configureSidebarEvents(): void {
|
||||
this.sidebarHiddenChange.subscribe(hidden => {
|
||||
this.sidebarHidden = hidden;
|
||||
});
|
||||
}
|
||||
|
||||
configureResizeEvents(): void {
|
||||
this.sizeChange.subscribe(size => this.size = size);
|
||||
this.sizeChange.next(WindowSize.generate());
|
||||
fromEvent(window, 'resize').subscribe(e => {
|
||||
this.sizeChange.next(WindowSize.generate());
|
||||
});
|
||||
}
|
||||
|
||||
configureLanguageEvents(): void {
|
||||
this.langChange.subscribe(lang => {
|
||||
this.lang = lang;
|
||||
this.browserStorageService.setItem('language', lang);
|
||||
});
|
||||
let language = this.browserStorageService.getItem('language');
|
||||
if (!language) {
|
||||
language = this.defaultLang;
|
||||
}
|
||||
this.translate.setDefaultLang(this.defaultLang);
|
||||
this.changeLang(language);
|
||||
}
|
||||
|
||||
addDynamicToolbarComponent(component): void {
|
||||
this.dynamicToolbarComponents.push(component);
|
||||
this.dynamicToolbarComponentsChange.next(this.dynamicToolbarComponents);
|
||||
}
|
||||
|
||||
removeDynamicToolbarComponent(component): void {
|
||||
this.dynamicToolbarComponents = this.dynamicToolbarComponents.filter(c => c !== component);
|
||||
this.dynamicToolbarComponentsChange.next(this.dynamicToolbarComponents);
|
||||
}
|
||||
|
||||
setSidebarHidden(hidden: boolean): void {
|
||||
this.sidebarHiddenChange.next(hidden);
|
||||
}
|
||||
|
||||
getSidebarHidden(): boolean {
|
||||
return this.sidebarHidden;
|
||||
}
|
||||
|
||||
changeLang(lang: string): void {
|
||||
if (lang !== this.lang) {
|
||||
this.translate.use(lang);
|
||||
this.langChange.next(lang);
|
||||
}
|
||||
}
|
||||
|
||||
getLang() {
|
||||
getLang(): string {
|
||||
return this.lang;
|
||||
}
|
||||
|
||||
getLangs() {
|
||||
getLangs(): string[] {
|
||||
return [
|
||||
'pl',
|
||||
'en',
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { BrowserStorageService } from 'src/app/modules/shared/services/browser-storage/browser-storage.service';
|
||||
|
||||
@Injectable()
|
||||
export class AuthTokenService {
|
||||
private TokenKey = 'jwt.token';
|
||||
|
||||
constructor( private browserStorageService: BrowserStorageService) {
|
||||
}
|
||||
|
||||
setToken(token: string): void {
|
||||
this.browserStorageService.setItem(this.TokenKey, token);
|
||||
}
|
||||
|
||||
hasToken(): boolean {
|
||||
const token = this.browserStorageService.getItemOrDefault(this.TokenKey, null);
|
||||
return token !== null;
|
||||
}
|
||||
|
||||
getToken(): string {
|
||||
return this.browserStorageService.getItem(this.TokenKey);
|
||||
}
|
||||
|
||||
removeToken(): void {
|
||||
this.browserStorageService.setItem(this.TokenKey, null);
|
||||
}
|
||||
|
||||
userValidate(): boolean {
|
||||
if (!this.hasToken()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -5,12 +5,13 @@ export class BrowserStorageService {
|
|||
|
||||
private storage: Storage;
|
||||
private namespace: string;
|
||||
|
||||
constructor() {
|
||||
this.storage = localStorage;
|
||||
this.namespace = 'default';
|
||||
}
|
||||
|
||||
setNamespace(namespace: string) {
|
||||
setNamespace(namespace: string): void {
|
||||
this.namespace = namespace;
|
||||
}
|
||||
|
||||
|
@ -18,7 +19,7 @@ export class BrowserStorageService {
|
|||
return namespace ?? this.namespace;
|
||||
}
|
||||
|
||||
setItem(key: string, value: any, namespace: string | null = null) {
|
||||
setItem(key: string, value: any, namespace: string | null = null): void {
|
||||
namespace = this.getNamespace(namespace);
|
||||
const path = `${namespace}.${key}`;
|
||||
this.storage.setItem(path, JSON.stringify(value));
|
||||
|
|
|
@ -21,11 +21,11 @@ export class ThemeService {
|
|||
this.setTheme(userTheme);
|
||||
}
|
||||
|
||||
getTheme() {
|
||||
getTheme(): string {
|
||||
return this.theme;
|
||||
}
|
||||
|
||||
setTheme(theme: string) {
|
||||
setTheme(theme: string): void {
|
||||
if (theme !== this.theme) {
|
||||
this.themeChange.next(theme);
|
||||
document.getElementById('current-theme').setAttribute('href', `/${theme}.css`);
|
||||
|
@ -37,5 +37,5 @@ export class ThemeService {
|
|||
'light',
|
||||
'dark',
|
||||
];
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import { NotFoundComponent } from './components/not-found/not-found.component';
|
|||
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||
import { HttpLoaderFactory } from 'src/app/app.module';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { MaterialModule } from '../material/material.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
|
@ -11,6 +12,7 @@ import { HttpClient } from '@angular/common/http';
|
|||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
MaterialModule,
|
||||
TranslateModule.forRoot({
|
||||
defaultLanguage: 'en',
|
||||
loader: {
|
||||
|
|
|
@ -1,6 +1,38 @@
|
|||
@use 'sass:map';
|
||||
|
||||
$primary-color: map.get($theme, color, primary, 500);
|
||||
$toolbar-background: map.get($theme, color, background, app-bar);
|
||||
$toolbar-color: map.get($theme, color, primary, contrast, 500) !default;
|
||||
$main-background: map.get($theme, color, background, background) !default;
|
||||
$warn-color: map.get($theme, warn, 500) !default;
|
||||
body {
|
||||
--primary-color: #{$primary-color};
|
||||
--toolbar-background: #{$toolbar-background};
|
||||
--toolbar-color: #{$toolbar-color};
|
||||
--main-background: #{$main-background};
|
||||
--warn-color: #{$warn-color};
|
||||
.mat-toolbar{
|
||||
background-color: var(--toolbar-background);
|
||||
color: var(--toolbar-color);
|
||||
}
|
||||
.toolbar-background{
|
||||
position: relative;
|
||||
& > *{
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
&:before{
|
||||
position: absolute;
|
||||
z-index: 0;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
width:100%;
|
||||
height: 100%;
|
||||
content: "";
|
||||
opacity: 0.125;
|
||||
background-image: url('/assets/background/items.png');
|
||||
background-position: top;
|
||||
background-attachment: fixed;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,7 +11,7 @@
|
|||
// Define the palettes for your theme using the Material Design palettes available in palette.scss
|
||||
// (imported above). For each palette, you can optionally specify a default, lighter, and darker
|
||||
// hue. Available color palettes: https://material.io/design/color/
|
||||
$theme-primary: mat.define-palette(mat.$blue-palette);
|
||||
$theme-primary: mat.define-palette(mat.$indigo-palette);
|
||||
$theme-accent: mat.define-palette(mat.$green-palette, A200, A100, A400);
|
||||
|
||||
// The warn palette is optional (defaults to red).
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
@use "sass:map";
|
||||
// Custom Theming for Angular Material
|
||||
// For more information: https://material.angular.io/guide/theming
|
||||
@use '~@angular/material' as mat;
|
||||
|
@ -11,7 +12,7 @@
|
|||
// Define the palettes for your theme using the Material Design palettes available in palette.scss
|
||||
// (imported above). For each palette, you can optionally specify a default, lighter, and darker
|
||||
// hue. Available color palettes: https://material.io/design/color/
|
||||
$theme-primary: mat.define-palette(mat.$indigo-palette);
|
||||
$theme-primary: mat.define-palette(mat.$blue-palette);
|
||||
$theme-accent: mat.define-palette(mat.$light-blue-palette, A200, A100, A400);
|
||||
|
||||
// The warn palette is optional (defaults to red).
|
||||
|
@ -27,6 +28,10 @@ $theme: mat.define-light-theme((
|
|||
)
|
||||
));
|
||||
|
||||
$toolbar-background: map.get($theme, color, primary, 500);
|
||||
$toolbar-text: map.get($theme, color, primary, contrast, 500);
|
||||
$theme: map.set($theme, color, background, app-bar, $toolbar-background);
|
||||
$theme: map.set($theme, color, foreground, app-bar, $toolbar-text);
|
||||
// Include theme styles for core and each component used in your app.
|
||||
// Alternatively, you can import and @include the theme mixins for each component
|
||||
// that you are using.
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 104 KiB |
|
@ -19,7 +19,9 @@
|
|||
"HOME": "Dashboard",
|
||||
"PROFILE": "Profile",
|
||||
"COMMUNITY": "Community",
|
||||
"GROUP": "My group",
|
||||
"MESSAGES": "Messages",
|
||||
"QUESTIONS": "Q&A",
|
||||
"TESTS": "Laboratory tests",
|
||||
"RESEARCH": "Research",
|
||||
"ADMIN": "Admin",
|
||||
|
|
|
@ -19,7 +19,9 @@
|
|||
"HOME": "Kokpit",
|
||||
"PROFILE": "Profil",
|
||||
"COMMUNITY": "Społeczność",
|
||||
"GROUP": "Moja grupa",
|
||||
"MESSAGES": "Wiadomości",
|
||||
"QUESTIONS": "Pytania i odpowiedzi",
|
||||
"TESTS": "Wyniki badań",
|
||||
"RESEARCH": "Badania naukowe",
|
||||
"ADMIN": "Panel administratora",
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
export const environment = {
|
||||
production: true
|
||||
production: true,
|
||||
buildId: -1001,
|
||||
buildTime: -1002,
|
||||
};
|
||||
|
|
|
@ -3,7 +3,9 @@
|
|||
// The list of file replacements can be found in `angular.json`.
|
||||
|
||||
export const environment = {
|
||||
production: false
|
||||
production: false,
|
||||
buildId: -1001,
|
||||
buildTime: -1002,
|
||||
};
|
||||
|
||||
/*
|
||||
|
|
|
@ -43,3 +43,8 @@ body .mat-drawer.mat-drawer-side {
|
|||
.w-220{
|
||||
width: 220px;
|
||||
}
|
||||
[required]:after{
|
||||
content: "*";
|
||||
font-weight: bold;
|
||||
color: var(--warn-color);
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
name=value
|
||||
name2="value 2"
|
||||
#name3=value3
|
||||
name4 = value4
|
||||
name5 = "value 5"
|
|
@ -0,0 +1,80 @@
|
|||
<?php
|
||||
|
||||
namespace Tools;
|
||||
|
||||
class PutEnv {
|
||||
private string $file;
|
||||
private string $key;
|
||||
private string $value;
|
||||
private array $content;
|
||||
public function __construct() {
|
||||
$argv = $_SERVER['argv'];
|
||||
$argc = sizeof($argv);
|
||||
switch($argc) {
|
||||
case 4:
|
||||
$this->file = $argv[1];
|
||||
$this->key = $argv[2];
|
||||
$this->value = $argv[3];
|
||||
break;
|
||||
|
||||
case 3:
|
||||
$this->file = $argv[1];
|
||||
$this->key = $argv[2];
|
||||
$this->value = fgets(STDIN);
|
||||
break;
|
||||
|
||||
default:
|
||||
echo "usage:
|
||||
php8.0 putenv.php filename key value
|
||||
echo value | php8.0 putenv filename key";
|
||||
throw new Exception("invalid params");
|
||||
}
|
||||
if (!file_exists($this->file)) {
|
||||
throw new Exception('File not found');
|
||||
}
|
||||
$changed = 0;
|
||||
$lineWasEmpty = false;
|
||||
$key = $this->key;
|
||||
$value = $this->value;
|
||||
if (strpos($value, ' ') !== false) {
|
||||
$value = '"' . addslashes($value) . '"';
|
||||
}
|
||||
$this->content = file($this->file);
|
||||
foreach ($this->content as $l => $line) {
|
||||
if (strlen(trim($line)) < 1) {
|
||||
if ($lineWasEmpty) {
|
||||
$this->content[$l] = null;
|
||||
} else {
|
||||
$lineWasEmpty = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
$lineWasEmpty = false;
|
||||
if (!str_starts_with($line, $this->key)) {
|
||||
continue;
|
||||
}
|
||||
$next = trim(substr($line, strlen($this->key)));
|
||||
if (strlen($next) === 0 || $next[0] !== '=') {
|
||||
continue;
|
||||
}
|
||||
$this->content[$l] = $key . '=' . $value;
|
||||
$changed++;
|
||||
}
|
||||
if ($changed === 0) {
|
||||
$this->content[] = $key . '=' . $value;
|
||||
} else if ($changed > 1) {
|
||||
echo "Key $key changed $changed times\n";
|
||||
}
|
||||
$content = $this->content;
|
||||
$this->content = [];
|
||||
foreach ($content as $line) {
|
||||
if ($line !== null) {
|
||||
$this->content[] = $line;
|
||||
}
|
||||
}
|
||||
$content = implode('', $this->content);
|
||||
file_put_contents($this->file, $content);
|
||||
}
|
||||
}
|
||||
|
||||
new PutEnv();
|
Loading…
Reference in New Issue