Compare commits

..

4 Commits

Author SHA1 Message Date
Sieciech d686de2a1d Merge 2021-07-27 13:51:20 +02:00
Sieciech e82f488f9a update startup 2021-07-27 13:45:22 +02:00
Sieciech 3837371268 demo homepage and ui 2021-07-27 12:51:52 +02:00
Sieciech 8b483ef021 changes 2021-07-26 16:43:18 +02:00
67 changed files with 10282 additions and 11960 deletions

198
Jenkinsfile vendored
View File

@ -1,198 +0,0 @@
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/'
}
}
}
}
}
}
}

View File

@ -19,7 +19,9 @@ services:
- ./logs:/var/log - ./logs:/var/log
- vendor:/web/backend/vendor - vendor:/web/backend/vendor
- var:/web/backend/var - var:/web/backend/var
- nodemodules:/web/frontend/node_modules
volumes: volumes:
nodemodules: nodemodules:
vendor: vendor:
var: var:
nodemodules:

View File

@ -1,7 +1,8 @@
#!/bin/bash #!/bin/bash
echo "> Configure cache" echo "> Configure cache"
mkdir -p /tmp/app/vendor mkdir -p /tmp/app/{vendor,node_modules}
ln -s /tmp/app/node_modules /web/frontend/node_modules
ln -s /tmp/app/vendor /web/backend/vendor ln -s /tmp/app/vendor /web/backend/vendor
echo "> Configure user" echo "> Configure user"
@ -45,14 +46,10 @@ echo "> Configure symfony"
cd /web/backend cd /web/backend
composer install composer install
php bin/console doctrine:schema:update --force php bin/console doctrine:schema:update --force
php php bin/console lexik:jwt:generate-keypair --skip-if-exists
echo "> Configure angular" echo "> Configure angular"
cd /web/frontend cd /web/frontend
time su developer -c 'npm ci' su developer -c 'time npm ci'
echo "> Starting angular" echo "> Starting angular"
su developer -c 'ng serve --disable-host-check --host 0.0.0.0 --poll' su developer -c 'ng serve --disable-host-check --host 0.0.0.0 --poll'
echo "> Waiting"
sleep Infinity

View File

@ -30,9 +30,3 @@ APP_SECRET=0fc8d6b67b9f1100b3eb3e3c80d36fda
# DATABASE_URL="mysql://db_user:db_password@127.0.0.1:3306/db_name?serverVersion=5.7" # 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" DATABASE_URL="postgresql://db_user:db_password@127.0.0.1:5432/db_name?serverVersion=13&charset=utf8"
###< doctrine/doctrine-bundle ### ###< 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 ###

View File

@ -4,4 +4,3 @@ APP_SECRET='$ecretf0rt3st'
SYMFONY_DEPRECATIONS_HELPER=999999 SYMFONY_DEPRECATIONS_HELPER=999999
PANTHER_APP_ENV=panther PANTHER_APP_ENV=panther
PANTHER_ERROR_SCREENSHOT_DIR=./var/error-screenshots 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"

View File

@ -18,7 +18,3 @@
/phpunit.xml /phpunit.xml
.phpunit.result.cache .phpunit.result.cache
###< phpunit/phpunit ### ###< phpunit/phpunit ###
###> lexik/jwt-authentication-bundle ###
/config/jwt/*.pem
###< lexik/jwt-authentication-bundle ###

View File

@ -12,10 +12,8 @@
"doctrine/doctrine-bundle": "^2.4", "doctrine/doctrine-bundle": "^2.4",
"doctrine/doctrine-migrations-bundle": "^3.1", "doctrine/doctrine-migrations-bundle": "^3.1",
"doctrine/orm": "^2.9", "doctrine/orm": "^2.9",
"lexik/jwt-authentication-bundle": "^2.12",
"phpdocumentor/reflection-docblock": "^5.2", "phpdocumentor/reflection-docblock": "^5.2",
"sensio/framework-extra-bundle": "^6.1", "sensio/framework-extra-bundle": "^6.1",
"symfony-bundles/json-request-bundle": "^4.0",
"symfony/asset": "5.3.*", "symfony/asset": "5.3.*",
"symfony/console": "5.3.*", "symfony/console": "5.3.*",
"symfony/dotenv": "5.3.*", "symfony/dotenv": "5.3.*",

20335
src/backend/composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -12,6 +12,4 @@ return [
Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true], Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true], Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true], Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true],
Lexik\Bundle\JWTAuthenticationBundle\LexikJWTAuthenticationBundle::class => ['all' => true],
SymfonyBundles\JsonRequestBundle\JsonRequestBundle::class => ['all' => true],
]; ];

View File

@ -1,4 +0,0 @@
lexik_jwt_authentication:
secret_key: '%env(resolve:JWT_SECRET_KEY)%'
public_key: '%env(resolve:JWT_PUBLIC_KEY)%'
pass_phrase: '%env(JWT_PASSPHRASE)%'

View File

@ -13,29 +13,17 @@ security:
app_user_provider: app_user_provider:
entity: entity:
class: App\Entity\User class: App\Entity\User
property: email property: id
# used to reload user from session & other features (e.g. switch_user) # used to reload user from session & other features (e.g. switch_user)
# 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: 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: dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/ pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false security: false
main: main:
lazy: true lazy: true
provider: app_user_provider provider: app_user_provider
# activate different ways to authenticate # activate different ways to authenticate
# https://symfony.com/doc/current/security.html#firewalls-authentication # https://symfony.com/doc/current/security.html#firewalls-authentication
@ -47,6 +35,3 @@ security:
access_control: access_control:
# - { path: ^/admin, roles: ROLE_ADMIN } # - { path: ^/admin, roles: ROLE_ADMIN }
# - { path: ^/profile, roles: ROLE_USER } # - { path: ^/profile, roles: ROLE_USER }
# - { path: ^/register, roles: IS_AUTHENTICATED_ANONYMOUSLY }
# - { path: ^/api/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
# - { path: ^/api, roles: IS_AUTHENTICATED_FULLY }

View File

@ -1,3 +1 @@
cd ../../docker docker.exe compose -p curenet exec app bash -c 'cd /web/backend && bash'
docker.exe compose -p curenet exec app bash -c 'cd /web/backend && bash'
cd src/backend

View File

@ -1,66 +0,0 @@
<?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);
}
}

View File

@ -1,32 +0,0 @@
<?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;
}
}

View File

@ -2,21 +2,17 @@
namespace App\Entity; namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use App\Repository\UserRepository; use App\Repository\UserRepository;
use App\Entity\Abstraction\BaseEntity; use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;
use Lexik\Bundle\JWTAuthenticationBundle\Security\User\JWTUserInterface;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface;
/** /**
* @ORM\Entity(repositoryClass=UserRepository::class) * @ORM\Entity(repositoryClass=UserRepository::class)
* @ORM\Table(name="`user`") * @ORM\Table(name="`user`")
*/ */
class User extends BaseEntity implements UserInterface, PasswordAuthenticatedUserInterface, JWTUserInterface class User implements UserInterface, PasswordAuthenticatedUserInterface
{ {
protected array $hidden = ['password', 'salt', 'userIdentifier', 'username'];
/** /**
* @ORM\Id * @ORM\Id
* @ORM\GeneratedValue * @ORM\GeneratedValue
@ -33,44 +29,38 @@ class User extends BaseEntity implements UserInterface, PasswordAuthenticatedUse
* @var string The hashed password * @var string The hashed password
* @ORM\Column(type="string") * @ORM\Column(type="string")
*/ */
private $password; private $password;
/** /**
* @ORM\Column(type="string", length=255) * @ORM\Column(type="string", length=255)
*/ */
private $email; private $email;
/** /**
* @ORM\Column(type="string", length=255) * @ORM\Column(type="string", length=255)
*/ */
private $name; private $name;
/** /**
* @ORM\Column(type="string", length=255) * @ORM\Column(type="string", length=255)
*/ */
private $surname; private $surname;
/** /**
* @ORM\Column(type="string", length=255, nullable=true) * @ORM\Column(type="string", length=255, nullable=true)
*/ */
private $avatar; private $avatar;
/** /**
* @ORM\Column(type="string", length=3, nullable=true) * @ORM\Column(type="string", length=3, nullable=true)
*/ */
private $country; private $country;
/** /**
* @ORM\Column(type="string", length=10, nullable=true) * @ORM\Column(type="string", length=10, nullable=true)
*/ */
private $state; private $state;
public static function createFromPayload($username, array $payload)
{
$user = new User();
return $user;
}
public function getId(): ?string public function getId(): ?string
{ {
return $this->id; return $this->id;
@ -90,7 +80,7 @@ class User extends BaseEntity implements UserInterface, PasswordAuthenticatedUse
*/ */
public function getUserIdentifier(): string public function getUserIdentifier(): string
{ {
return (string) $this->email; return (string) $this->id;
} }
/** /**
@ -98,7 +88,7 @@ class User extends BaseEntity implements UserInterface, PasswordAuthenticatedUse
*/ */
public function getUsername(): string public function getUsername(): string
{ {
return (string) $this->email; return (string) $this->id;
} }
/** /**
@ -153,83 +143,77 @@ class User extends BaseEntity implements UserInterface, PasswordAuthenticatedUse
{ {
// If you store any temporary, sensitive data on the user, clear it here // If you store any temporary, sensitive data on the user, clear it here
// $this->plainPassword = null; // $this->plainPassword = null;
} }
public function getEmail(): ?string public function getEmail(): ?string
{ {
return $this->email; return $this->email;
} }
public function setEmail(string $email): self public function setEmail(string $email): self
{ {
$this->email = $email; $this->email = $email;
return $this; return $this;
} }
public function getName(): ?string public function getName(): ?string
{ {
return $this->name; return $this->name;
} }
public function setName(string $name): self public function setName(string $name): self
{ {
$this->name = $name; $this->name = $name;
return $this; return $this;
} }
public function getSurname(): ?string public function getSurname(): ?string
{ {
return $this->surname; return $this->surname;
} }
public function setSurname(string $surname): self public function setSurname(string $surname): self
{ {
$this->surname = $surname; $this->surname = $surname;
return $this; return $this;
} }
public function getAvatar(): ?string public function getAvatar(): ?string
{ {
return $this->avatar; return $this->avatar;
} }
public function setAvatar(?string $avatar): self public function setAvatar(?string $avatar): self
{ {
$this->avatar = $avatar; $this->avatar = $avatar;
return $this; return $this;
} }
public function getCountry(): ?string public function getCountry(): ?string
{ {
return $this->country; return $this->country;
} }
public function setCountry(?string $country): self public function setCountry(?string $country): self
{ {
$this->country = $country; $this->country = $country;
return $this; return $this;
} }
public function getState(): ?string public function getState(): ?string
{ {
return $this->state; return $this->state;
} }
public function setState(?string $state): self public function setState(?string $state): self
{ {
$this->state = $state; $this->state = $state;
return $this; return $this;
}
public function generateAvatar() {
if (!$this->avatar) {
$this->avatar = 'https://www.gravatar.com/avatar/' . md5($this->email) . '?s=514&d=robohash';
}
} }
} }

View File

@ -36,20 +36,6 @@ class UserRepository extends ServiceEntityRepository implements PasswordUpgrader
$this->_em->flush(); $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 // * @return User[] Returns an array of User objects
// */ // */

View File

@ -1,35 +0,0 @@
<?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);
}
}

View File

@ -91,33 +91,12 @@
"laminas/laminas-code": { "laminas/laminas-code": {
"version": "4.4.2" "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": { "monolog/monolog": {
"version": "2.3.1" "version": "2.3.1"
}, },
"myclabs/deep-copy": { "myclabs/deep-copy": {
"version": "1.10.2" "version": "1.10.2"
}, },
"namshi/jose": {
"version": "7.2.3"
},
"nikic/php-parser": { "nikic/php-parser": {
"version": "v4.12.0" "version": "v4.12.0"
}, },
@ -243,9 +222,6 @@
"config/packages/sensio_framework_extra.yaml" "config/packages/sensio_framework_extra.yaml"
] ]
}, },
"symfony-bundles/json-request-bundle": {
"version": "4.0.5"
},
"symfony/asset": { "symfony/asset": {
"version": "v5.3.2" "version": "v5.3.2"
}, },
@ -466,9 +442,6 @@
"symfony/polyfill-mbstring": { "symfony/polyfill-mbstring": {
"version": "v1.23.0" "version": "v1.23.0"
}, },
"symfony/polyfill-php56": {
"version": "v1.20.0"
},
"symfony/polyfill-php73": { "symfony/polyfill-php73": {
"version": "v1.23.0" "version": "v1.23.0"
}, },

View File

@ -1715,14 +1715,6 @@
"schema-utils": "^2.7.0" "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": { "@ngtools/webpack": {
"version": "12.1.3", "version": "12.1.3",
"resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-12.1.3.tgz", "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-12.1.3.tgz",

View File

@ -21,7 +21,6 @@
"@angular/platform-browser": "~12.1.3", "@angular/platform-browser": "~12.1.3",
"@angular/platform-browser-dynamic": "~12.1.3", "@angular/platform-browser-dynamic": "~12.1.3",
"@angular/router": "~12.1.3", "@angular/router": "~12.1.3",
"@ng-stack/forms": "^2.4.0",
"@ngx-translate/core": "^13.0.0", "@ngx-translate/core": "^13.0.0",
"@ngx-translate/http-loader": "^6.0.0", "@ngx-translate/http-loader": "^6.0.0",
"bootstrap": "^5.0.2", "bootstrap": "^5.0.2",

View File

@ -1,134 +1,102 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { Route, RouterModule } from '@angular/router'; 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'; import { NotFoundComponent } from './modules/shared/components/not-found/not-found.component';
interface AppMenuEntry { interface AppMenuEntry {
label: string; label: string;
icon: string; icon: string;
path?: string; path?: string;
matchExact?: boolean; matchExact?: boolean;
level?: number; level?: number;
} }
interface AppRoute extends Route { interface AppRoute extends Route {
menuEntries?: AppMenuEntry[]; menuEntries?: AppMenuEntry[];
} }
export const appRoutes: AppRoute[] = [ export const appRoutes: AppRoute[] = [
{ {
path: 'auth', path: '',
loadChildren: () => import('./modules/auth/auth.module').then(m => m.AuthModule), component: NotFoundComponent,
}, menuEntries: [
{ {
path: '', label: 'MENU.HOME',
component: NotFoundComponent, icon: 'fa fa-home',
menuEntries: [ matchExact: true,
{ },
label: 'MENU.HOME', ],
icon: 'fa fa-home', },
matchExact: true, {
}, path: 'profile',
], component: NotFoundComponent,
canActivate: [ AuthGuard, ], menuEntries: [
canActivateChild: [ AuthGuard, ], {
}, label: 'MENU.PROFILE',
{ icon: 'fa fa-user',
path: 'profile', },
component: NotFoundComponent, ],
menuEntries: [ },
{ {
label: 'MENU.PROFILE', path: 'community',
icon: 'fa fa-user', component: NotFoundComponent,
}, menuEntries: [
], {
canActivate: [ AuthGuard, ], label: 'MENU.COMMUNITY',
canActivateChild: [ AuthGuard, ], icon: 'fa fa-users',
}, },
{ ],
path: 'community', },
component: NotFoundComponent, {
menuEntries: [ path: 'messages',
{ component: NotFoundComponent,
label: 'MENU.COMMUNITY', menuEntries: [
icon: 'fa fa-users', {
}, label: 'MENU.MESSAGES',
], icon: 'fa fa-comments',
canActivate: [ AuthGuard, ], },
canActivateChild: [ AuthGuard, ], ],
}, },
{ {
path: 'questions', path: 'tests',
component: NotFoundComponent, component: NotFoundComponent,
menuEntries: [ menuEntries: [
{ {
label: 'MENU.QUESTIONS', label: 'MENU.TESTS',
icon: 'fa fa-question', icon: 'fa fa-heartbeat',
level: 1, },
} ],
], },
canActivate: [ AuthGuard, ], {
canActivateChild: [ AuthGuard, ], path: 'research',
}, component: NotFoundComponent,
{ menuEntries: [
path: 'messages', {
component: NotFoundComponent, label: 'MENU.RESEARCH',
menuEntries: [ icon: 'fa fa-flask',
{ },
label: 'MENU.MESSAGES', ],
icon: 'fa fa-comments', },
}, {
], path: 'admin',
canActivate: [ AuthGuard, ], loadChildren: () => import('./modules/admin/admin.module').then(m => m.AdminModule),
canActivateChild: [ AuthGuard, ], menuEntries: [
}, {
{ label: 'MENU.ADMIN',
path: 'tests', icon: 'fa fa-id-card',
component: NotFoundComponent, matchExact: true,
menuEntries: [ },
{ {
label: 'MENU.TESTS', path: 'admin/users',
icon: 'fa fa-heartbeat', label: 'MENU.USERS',
}, icon: 'fa fa-users',
], level: 1,
canActivate: [ AuthGuard, ], }
canActivateChild: [ AuthGuard, ], ],
}, },
{
path: 'research',
component: NotFoundComponent,
menuEntries: [
{
label: 'MENU.RESEARCH',
icon: 'fa fa-flask',
},
],
canActivate: [ AuthGuard, ],
canActivateChild: [ AuthGuard, ],
},
{
path: 'admin',
loadChildren: () => import('./modules/admin/admin.module').then(m => m.AdminModule),
menuEntries: [
{
label: 'MENU.ADMIN',
icon: 'fa fa-id-card',
matchExact: true,
},
{
path: 'admin/users',
label: 'MENU.USERS',
icon: 'fa fa-users',
level: 1,
}
],
canActivate: [ AuthGuard, ],
canActivateChild: [ AuthGuard, ],
},
]; ];
@NgModule({ @NgModule({
imports: [RouterModule.forRoot(appRoutes)], imports: [RouterModule.forRoot(appRoutes)],
exports: [RouterModule] exports: [RouterModule]
}) })
export class AppRoutingModule { } export class AppRoutingModule { }

View File

@ -1,23 +1,18 @@
<ng-template #toolbar> <ng-template #toolbar>
<mat-toolbar class="toolbar-background"> <mat-toolbar [color]="isDark ? null : 'primary'">
<button mat-icon-button (click)="toggleSidebar()" *ngIf="!isSidebarHidden"> <button mat-icon-button (click)="toggleSidebar()">
<i class="fa fa-bars"></i> <i class="fa fa-bars"></i>
</button> </button>
<span>{{ 'APP.NAME' | translate }}</span> <span>{{ 'APP.NAME' | translate }}</span>
<span class="flex-grow-1"></span> <span class="flex-grow-1"></span>
<div>
<div *ngFor="let component of dynamicToolbarComponents">
<ng-container [ngComponentOutlet]="component"></ng-container>
</div>
</div>
</mat-toolbar> </mat-toolbar>
</ng-template> </ng-template>
<ng-container *ngIf="!isMobile"> <ng-container *ngIf="!isMobile">
<ng-container *ngTemplateOutlet="toolbar"></ng-container> <ng-container *ngTemplateOutlet="toolbar"></ng-container>
</ng-container> </ng-container>
<mat-drawer-container class="flex-grow-1"> <mat-drawer-container class="flex-grow-1">
<mat-drawer [mode]="sidebarMode" [opened]="isSidebarOpen" (openedChange)="onSidebarOpenedChange($event)" *ngIf="!isSidebarHidden"> <mat-drawer [mode]="sidebarMode" [opened]="isSidebarOpen" (openedChange)="onSidebarOpenedChange($event)">
<div class="menu"> <div class="menu">
<div> <div>
<ng-container *ngFor="let route of appRoutes"> <ng-container *ngFor="let route of appRoutes">
@ -34,9 +29,10 @@
</div> </div>
</ng-container> </ng-container>
</ng-container> </ng-container>
</div> </div>
<div> <div>
<div class="d-flex user-box" *ngIf="user"> <div class="d-flex user-box">
<div class="avatar" [style.background-image]="url(user.avatar)"></div> <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="d-flex align-items-center flex-grow-1 p-2">{{ user.name }} {{ user.surname }}</div>
<div class="user-menu-container"> <div class="user-menu-container">
@ -52,23 +48,21 @@
<mat-option *ngFor="let lang of langs" [value]="lang">{{ 'LANGUAGE.'+lang.toUpperCase() | translate }}</mat-option> <mat-option *ngFor="let lang of langs" [value]="lang">{{ 'LANGUAGE.'+lang.toUpperCase() | translate }}</mat-option>
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
<mat-form-field class="w-100 px-3"> <mat-form-field class="w-100 px-3">
<mat-label>{{ 'APP.THEME' | translate }}</mat-label> <mat-label>{{ 'APP.THEME' | translate }}</mat-label>
<mat-select [formControl]="themeControl"> <mat-select [formControl]="themeControl">
<mat-option *ngFor="let theme of themes" [value]="theme">{{ 'THEME.'+theme.toUpperCase() | translate }}</mat-option> <mat-option *ngFor="let theme of themes" [value]="theme">{{ 'THEME.'+theme.toUpperCase() | translate }}</mat-option>
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
<button mat-menu-item (click)="logout()">
Wyloguj
</button>
</mat-menu> </mat-menu>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</mat-drawer> </mat-drawer>
<ng-container *ngIf="isMobile"> <ng-container *ngIf="isMobile">
<ng-container *ngTemplateOutlet="toolbar"></ng-container> <ng-container *ngTemplateOutlet="toolbar"></ng-container>
</ng-container> </ng-container>

View File

@ -5,16 +5,12 @@ import { AppService, WindowSize } from './modules/shared/services/app/app.servic
import { BrowserStorageService } from './modules/shared/services/browser-storage/browser-storage.service'; import { BrowserStorageService } from './modules/shared/services/browser-storage/browser-storage.service';
import { ThemeService } from './modules/shared/services/theme/theme.service'; import { ThemeService } from './modules/shared/services/theme/theme.service';
import { DomSanitizer, SafeStyle } from '@angular/platform-browser'; 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 { enum SidebarOpenEnum {
Default, Default,
Open, Open,
Closed, Closed,
} }
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
templateUrl: './app.component.html', templateUrl: './app.component.html',
@ -32,109 +28,83 @@ export class AppComponent {
themes: string[] = []; themes: string[] = [];
themeControl = new FormControl(); themeControl = new FormControl();
langControl = new FormControl(); langControl = new FormControl();
isDark = true;
appRoutes = appRoutes; appRoutes = appRoutes;
user: UserModel; user = {
isSidebarHidden = false; name: 'Name',
dynamicToolbarComponents = []; surname: 'Surname',
avatar: 'https://www.gravatar.com/avatar/81b206a89f89d5b1123b87606075c6a8?s=514&d=robohash',
};
get isSidebarOpen(): boolean { get isSidebarOpen() {
switch (this.sidebarOpen) { switch (this.sidebarOpen) {
case SidebarOpenEnum.Open: case SidebarOpenEnum.Open:
return true; return true;
break;
case SidebarOpenEnum.Closed: case SidebarOpenEnum.Closed:
return false; return false;
break;
default: default:
return this.defaultSidebarOpen; return this.defaultSidebarOpen;
break;
} }
} }
constructor( constructor(
private appService: AppService, appService: AppService,
private themeService: ThemeService, themeService: ThemeService,
private browserStorageService: BrowserStorageService, private browserStorageService: BrowserStorageService,
private sanitizer: DomSanitizer, private sanitizer: DomSanitizer,
private authService: AuthService,
private router: Router,
) { ) {
this.configureSidebarEvents(); this.themes = themeService.getThemes();
this.configureThemeEvents(); this.langs = appService.getLangs();
this.configureLanguageEvents(); this.lang = appService.getLang();
this.configureResizeEvents();
this.configureUserEvents();
}
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;
});
}
configureThemeEvents(): void {
this.themes = this.themeService.getThemes();
this.themeControl.valueChanges.subscribe(theme => { this.themeControl.valueChanges.subscribe(theme => {
this.themeService.setTheme(theme); themeService.setTheme(theme);
}); });
this.onThemeChanged(this.themeService.getTheme()); appService.changeLang(this.lang);
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.langControl.valueChanges.subscribe(lang => {
this.appService.changeLang(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);
}); });
this.onLangChanged(this.appService.getLang()); const sidebarUserPrefference = this.browserStorageService.getItem('sidebar.open');
this.appService.langChange.subscribe(lang => { if (sidebarUserPrefference !== null) {
this.onLangChanged(lang); this.sidebarOpen = sidebarUserPrefference;
}); }
} }
configureResizeEvents(): void { onThemeChanged(theme: string) {
this.onResize(this.appService.size);
this.appService.sizeChange.subscribe(size => {
this.onResize(size);
});
}
onThemeChanged(theme: string): void {
if (theme === undefined) { if (theme === undefined) {
return; return;
} }
this.theme = theme; this.theme = theme;
this.themeControl.setValue(theme); this.themeControl.setValue(theme);
this.isDark = theme.indexOf('dark') !== -1;
} }
onResize(size: WindowSize): void { onResize(size: WindowSize) {
if (size === undefined) { if (size === undefined) {
return; return;
} }
this.isMobile = size.isMobile; this.isMobile = size.isMobile;
this.defaultSidebarOpen = !size.isMobile; this.defaultSidebarOpen = !size.isMobile;
this.sidebarMode = this.isMobile ? 'over' : 'side'; this.sidebarMode = this.isMobile ? 'over' : 'side';
} }
onLangChanged(lang: string): void { onLangChanged(lang: string) {
if (lang === undefined) { if (lang === undefined) {
return; return;
} }
@ -142,21 +112,16 @@ export class AppComponent {
this.langControl.setValue(lang); this.langControl.setValue(lang);
} }
toggleSidebar(): void { toggleSidebar() {
this.sidebarOpen = this.isSidebarOpen ? SidebarOpenEnum.Closed : SidebarOpenEnum.Open; this.sidebarOpen = this.isSidebarOpen ? SidebarOpenEnum.Closed : SidebarOpenEnum.Open;
this.browserStorageService.setItem('sidebar.open', this.sidebarOpen); this.browserStorageService.setItem('sidebar.open', this.sidebarOpen);
} }
onSidebarOpenedChange(opened: boolean): void { onSidebarOpenedChange(opened: boolean) {
this.sidebarOpen = opened ? SidebarOpenEnum.Open : SidebarOpenEnum.Closed; this.sidebarOpen = opened ? SidebarOpenEnum.Open : SidebarOpenEnum.Closed;
} }
url(url: string): SafeStyle { url(url: string): SafeStyle {
return this.sanitizer.bypassSecurityTrustStyle(`url('${url}')`); return this.sanitizer.bypassSecurityTrustStyle(`url('${url}')`);
} }
}
logout(): void {
this.authService.logout();
this.router.navigateByUrl('/');
}
}

View File

@ -1,6 +1,6 @@
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { HttpClientModule, HttpClient, HTTP_INTERCEPTORS } from '@angular/common/http'; import { HttpClientModule, HttpClient } from '@angular/common/http';
import { AppRoutingModule } from './app-routing.module'; import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
@ -11,11 +11,8 @@ import { BrowserStorageService } from './modules/shared/services/browser-storage
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { TranslateHttpLoader } from '@ngx-translate/http-loader'; 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): TranslateHttpLoader { export function HttpLoaderFactory(http: HttpClient) {
return new TranslateHttpLoader(http, '/assets/lang/', '.json'); return new TranslateHttpLoader(http, '/assets/lang/', '.json');
} }
@ -44,9 +41,6 @@ export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader {
AppService, AppService,
ThemeService, ThemeService,
BrowserStorageService, BrowserStorageService,
AuthTokenService,
AuthService,
{ provide: HTTP_INTERCEPTORS, useClass: TokenInterceptor, multi: true }
], ],
bootstrap: [AppComponent] bootstrap: [AppComponent]
}) })

View File

@ -1,7 +1,6 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { RouterModule, Route } from '@angular/router'; import { RouterModule, Route } from '@angular/router';
import { NotFoundComponent } from '../shared/components/not-found/not-found.component'; import { NotFoundComponent } from '../shared/components/not-found/not-found.component';
import { UsersComponent } from './components/users/users.component';
const routes: Route[] = [ const routes: Route[] = [
{ {
@ -10,7 +9,7 @@ const routes: Route[] = [
}, },
{ {
path: 'users', path: 'users',
component: UsersComponent, component: NotFoundComponent,
}, },
]; ];

View File

@ -3,21 +3,14 @@ import { CommonModule } from '@angular/common';
import { AdminRoutingModule } from './admin-routing.module'; import { AdminRoutingModule } from './admin-routing.module';
import { SharedModule } from '../shared/shared.module'; import { SharedModule } from '../shared/shared.module';
import { UsersComponent } from './components/users/users.component';
import { AdminUserService } from './services/admin-user/admin-user.service';
@NgModule({ @NgModule({
declarations: [ declarations: [],
UsersComponent
],
imports: [ imports: [
CommonModule, CommonModule,
AdminRoutingModule, AdminRoutingModule,
SharedModule, SharedModule,
], ]
providers: [
AdminUserService,
],
}) })
export class AdminModule { } export class AdminModule { }

View File

@ -1 +0,0 @@
<p>users works!</p>

View File

@ -1,18 +0,0 @@
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(() => {
});
}
}

View File

@ -1,14 +0,0 @@
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');
}
}

View File

@ -1,31 +0,0 @@
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 { }

View File

@ -1,29 +0,0 @@
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 { }

View File

@ -1,66 +0,0 @@
<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>

View File

@ -1,9 +0,0 @@
.primary-background{
background-color: var(--toolbar-background);
height: 200px;
}
.content{
margin: 0px auto;
margin-top: -150px;
height: 200px;
}

View File

@ -1,118 +0,0 @@
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);
});
}
}

View File

@ -1,3 +0,0 @@
<button mat-icon-button (click)="toggleTheme()">
<i class="fa" [class.fa-sun-o]="isDark" [class.fa-moon-o]="!isDark"></i>
</button>

View File

@ -1,32 +0,0 @@
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;
}
}

View File

@ -1,5 +0,0 @@
export enum AuthTabEnum {
Login = 'login',
Register = 'register',
RestorePassword = 'restore-password',
}

View File

@ -1,4 +0,0 @@
export interface LoginModel {
email: string;
password: string;
}

View File

@ -1,6 +0,0 @@
export interface RegisterModel {
name: string;
surname: string;
email: string;
password: string;
}

View File

@ -1,3 +0,0 @@
export interface RestoreModel {
email: string;
}

View File

@ -1,3 +0,0 @@
export interface TokenResponse {
token: string;
}

View File

@ -1,8 +0,0 @@
export interface UserModel {
id: number;
name: string;
surname: string;
avatar: string | null;
country: string | null;
state: string | null;
}

View File

@ -1,34 +0,0 @@
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;
}
}

View File

@ -1,68 +0,0 @@
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']);
}
}

View File

@ -5,29 +5,23 @@ import {MatButtonModule} from '@angular/material/button';
import {MatSidenavModule} from '@angular/material/sidenav'; import {MatSidenavModule} from '@angular/material/sidenav';
import {MatSelectModule} from '@angular/material/select'; import {MatSelectModule} from '@angular/material/select';
import {MatMenuModule} from '@angular/material/menu'; 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 = [ const itemsToExport = [
MatToolbarModule, MatToolbarModule,
MatButtonModule, MatButtonModule,
MatSidenavModule, MatSidenavModule,
MatSelectModule, MatSelectModule,
MatMenuModule, MatMenuModule,
MatCardModule,
MatTabsModule,
MatInputModule,
]; ];
@NgModule({ @NgModule({
declarations: [], declarations: [],
imports: [ imports: [
CommonModule, CommonModule,
...itemsToExport, ...itemsToExport,
], ],
exports: [ exports: [
...itemsToExport, ...itemsToExport,
] ]
}) })
export class MaterialModule { } export class MaterialModule { }

View File

@ -1,10 +1,3 @@
<div class="primary-background toolbar-background"></div> <div class="m-3 p-3">
<div class="content align-items-center justify-content-center"> {{ 'ERROR.PAGE_NOT_FOUND' | translate }}
<div class="col-11 col-lg-7 col-xl-5"> </div>
<mat-card>
<div class="m-3 p-3">
{{ 'ERROR.PAGE_NOT_FOUND' | translate }}
</div>
</mat-card>
</div>
</div>

View File

@ -1,10 +0,0 @@
.primary-background{
background-color: var(--toolbar-background);
height: 100px;
}
.content{
margin: 0px auto;
margin-top: -100px;
height: 200px;
display: flex;
}

View File

@ -1,20 +0,0 @@
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);
}
}

View File

@ -1,9 +0,0 @@
export class User {
id: number;
email: string;
name: string;
surname: string;
avatar: string;
country: string;
state: string;
}

View File

@ -4,107 +4,69 @@ import { fromEvent, Subject } from 'rxjs';
import { BrowserStorageService } from '../browser-storage/browser-storage.service'; import { BrowserStorageService } from '../browser-storage/browser-storage.service';
export class WindowSize { export class WindowSize {
isMobile: boolean; isMobile: boolean;
constructor( constructor(
public width: number, public width: number,
public height: number, public height: number,
) { ) {
this.isMobile = this.width <= 700; this.isMobile = this.width <= 700;
} }
static generate(): WindowSize { update() {
return new WindowSize(window.innerWidth, window.innerHeight); this.width = window.innerWidth;
} this.height = window.innerHeight;
}
update(): WindowSize { static generate() {
this.width = window.innerWidth; return new WindowSize(window.innerWidth, window.innerHeight);
this.height = window.innerHeight; }
return this;
}
} }
@Injectable() @Injectable()
export class AppService { export class AppService {
private sidebarHidden = false; private lang = 'en';
private lang = 'en'; private defaultLang = 'en';
private defaultLang = 'en'; langChange = new Subject<string>();
private dynamicToolbarComponents = []; size: WindowSize;
langChange = new Subject<string>(); sizeChange = new Subject<WindowSize>();
size: WindowSize;
sizeChange = new Subject<WindowSize>();
sidebarHiddenChange = new Subject<boolean>();
dynamicToolbarComponentsChange = new Subject<any[]>();
constructor( constructor(
private browserStorageService: BrowserStorageService, browserStorageService: BrowserStorageService,
private translate: TranslateService, private translate: TranslateService,
) { ) {
this.configureResizeEvents(); this.langChange.subscribe(lang => {
this.configureLanguageEvents(); this.lang = lang;
this.configureSidebarEvents(); 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());
})
}
configureSidebarEvents(): void { changeLang(lang: string) {
this.sidebarHiddenChange.subscribe(hidden => { if (lang !== this.lang) {
this.sidebarHidden = hidden; this.translate.use(lang);
}); this.langChange.next(lang);
} }
}
configureResizeEvents(): void { getLang() {
this.sizeChange.subscribe(size => this.size = size); return this.lang;
this.sizeChange.next(WindowSize.generate()); }
fromEvent(window, 'resize').subscribe(e => {
this.sizeChange.next(WindowSize.generate());
});
}
configureLanguageEvents(): void { getLangs() {
this.langChange.subscribe(lang => { return [
this.lang = lang; 'pl',
this.browserStorageService.setItem('language', lang); 'en',
}); ];
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(): string {
return this.lang;
}
getLangs(): string[] {
return [
'pl',
'en',
];
}
} }

View File

@ -1,34 +0,0 @@
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;
}
}

View File

@ -5,13 +5,12 @@ export class BrowserStorageService {
private storage: Storage; private storage: Storage;
private namespace: string; private namespace: string;
constructor() { constructor() {
this.storage = localStorage; this.storage = localStorage;
this.namespace = 'default'; this.namespace = 'default';
} }
setNamespace(namespace: string): void { setNamespace(namespace: string) {
this.namespace = namespace; this.namespace = namespace;
} }
@ -19,7 +18,7 @@ export class BrowserStorageService {
return namespace ?? this.namespace; return namespace ?? this.namespace;
} }
setItem(key: string, value: any, namespace: string | null = null): void { setItem(key: string, value: any, namespace: string | null = null) {
namespace = this.getNamespace(namespace); namespace = this.getNamespace(namespace);
const path = `${namespace}.${key}`; const path = `${namespace}.${key}`;
this.storage.setItem(path, JSON.stringify(value)); this.storage.setItem(path, JSON.stringify(value));

View File

@ -21,11 +21,11 @@ export class ThemeService {
this.setTheme(userTheme); this.setTheme(userTheme);
} }
getTheme(): string { getTheme() {
return this.theme; return this.theme;
} }
setTheme(theme: string): void { setTheme(theme: string) {
if (theme !== this.theme) { if (theme !== this.theme) {
this.themeChange.next(theme); this.themeChange.next(theme);
document.getElementById('current-theme').setAttribute('href', `/${theme}.css`); document.getElementById('current-theme').setAttribute('href', `/${theme}.css`);
@ -37,5 +37,5 @@ export class ThemeService {
'light', 'light',
'dark', 'dark',
]; ];
} };
} }

View File

@ -4,7 +4,6 @@ import { NotFoundComponent } from './components/not-found/not-found.component';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { HttpLoaderFactory } from 'src/app/app.module'; import { HttpLoaderFactory } from 'src/app/app.module';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { MaterialModule } from '../material/material.module';
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -12,7 +11,6 @@ import { MaterialModule } from '../material/material.module';
], ],
imports: [ imports: [
CommonModule, CommonModule,
MaterialModule,
TranslateModule.forRoot({ TranslateModule.forRoot({
defaultLanguage: 'en', defaultLanguage: 'en',
loader: { loader: {

View File

@ -1,38 +1,6 @@
@use 'sass:map'; @use 'sass:map';
$primary-color: map.get($theme, color, primary, 500); $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 { body {
--primary-color: #{$primary-color}; --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;
}
}
}

View File

@ -11,7 +11,7 @@
// Define the palettes for your theme using the Material Design palettes available in palette.scss // 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 // (imported above). For each palette, you can optionally specify a default, lighter, and darker
// hue. Available color palettes: https://material.io/design/color/ // 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.$green-palette, A200, A100, A400); $theme-accent: mat.define-palette(mat.$green-palette, A200, A100, A400);
// The warn palette is optional (defaults to red). // The warn palette is optional (defaults to red).

View File

@ -1,4 +1,3 @@
@use "sass:map";
// Custom Theming for Angular Material // Custom Theming for Angular Material
// For more information: https://material.angular.io/guide/theming // For more information: https://material.angular.io/guide/theming
@use '~@angular/material' as mat; @use '~@angular/material' as mat;
@ -12,7 +11,7 @@
// Define the palettes for your theme using the Material Design palettes available in palette.scss // 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 // (imported above). For each palette, you can optionally specify a default, lighter, and darker
// hue. Available color palettes: https://material.io/design/color/ // 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.$light-blue-palette, A200, A100, A400); $theme-accent: mat.define-palette(mat.$light-blue-palette, A200, A100, A400);
// The warn palette is optional (defaults to red). // The warn palette is optional (defaults to red).
@ -28,10 +27,6 @@ $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. // Include theme styles for core and each component used in your app.
// Alternatively, you can import and @include the theme mixins for each component // Alternatively, you can import and @include the theme mixins for each component
// that you are using. // that you are using.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

View File

@ -19,9 +19,7 @@
"HOME": "Dashboard", "HOME": "Dashboard",
"PROFILE": "Profile", "PROFILE": "Profile",
"COMMUNITY": "Community", "COMMUNITY": "Community",
"GROUP": "My group",
"MESSAGES": "Messages", "MESSAGES": "Messages",
"QUESTIONS": "Q&A",
"TESTS": "Laboratory tests", "TESTS": "Laboratory tests",
"RESEARCH": "Research", "RESEARCH": "Research",
"ADMIN": "Admin", "ADMIN": "Admin",

View File

@ -19,9 +19,7 @@
"HOME": "Kokpit", "HOME": "Kokpit",
"PROFILE": "Profil", "PROFILE": "Profil",
"COMMUNITY": "Społeczność", "COMMUNITY": "Społeczność",
"GROUP": "Moja grupa",
"MESSAGES": "Wiadomości", "MESSAGES": "Wiadomości",
"QUESTIONS": "Pytania i odpowiedzi",
"TESTS": "Wyniki badań", "TESTS": "Wyniki badań",
"RESEARCH": "Badania naukowe", "RESEARCH": "Badania naukowe",
"ADMIN": "Panel administratora", "ADMIN": "Panel administratora",

View File

@ -1,5 +1,3 @@
export const environment = { export const environment = {
production: true, production: true
buildId: -1001,
buildTime: -1002,
}; };

View File

@ -3,9 +3,7 @@
// The list of file replacements can be found in `angular.json`. // The list of file replacements can be found in `angular.json`.
export const environment = { export const environment = {
production: false, production: false
buildId: -1001,
buildTime: -1002,
}; };
/* /*

View File

@ -42,9 +42,4 @@ body .mat-drawer.mat-drawer-side {
} }
.w-220{ .w-220{
width: 220px; width: 220px;
}
[required]:after{
content: "*";
font-weight: bold;
color: var(--warn-color);
} }

View File

@ -1,5 +0,0 @@
name=value
name2="value 2"
#name3=value3
name4 = value4
name5 = "value 5"

View File

@ -1,80 +0,0 @@
<?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();