From e99aa9b8f17e888682f46eef54b655f0f8da2c0d Mon Sep 17 00:00:00 2001 From: amirghasempoor Date: Sun, 26 Apr 2026 16:25:59 +0330 Subject: [PATCH 01/61] use nginx and php-fpm for docker --- backend/Dockerfile | 44 ++++++++++++++++++++--------------- backend/Dockerfile.production | 23 ++++++++++++------ docker-compose.production.yml | 31 +++++++++++++++++------- docker-compose.yml | 28 +++++++++++++++++----- nginx/payment.conf | 21 +++++++++++++++++ 5 files changed, 106 insertions(+), 41 deletions(-) create mode 100644 nginx/payment.conf diff --git a/backend/Dockerfile b/backend/Dockerfile index c1d77b0f..e58f4534 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,28 +1,34 @@ -FROM docker.arvancloud.ir/dunglas/frankenphp:1.11.2-php8.5-trixie +FROM docker.arvancloud.ir/php:8.5-fpm-alpine3.23 -RUN apt-get update \ - && apt-get install -y --no-install-recommends libpq-dev \ +# Install common PHP extension dependencies +RUN apk update && apk add --no-cache \ + bash \ + curl \ + libpng \ + libpng-dev \ + libjpeg-turbo-dev \ + freetype-dev \ + libwebp-dev \ + libxml2-dev \ unzip \ - zip \ - && install-php-extensions pdo_pgsql pgsql \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* + libzip-dev \ + libpq-dev \ + && docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp \ + && docker-php-ext-install pdo_pgsql pdo_mysql gd zip +# Set the working directory WORKDIR /var/www/payment - -#ENV SERVER_NAME=your-domain-name.example.com - -# Copy Laravel application COPY . . -# Give permissions to storage and bootstrap/cache -RUN chmod -R 777 storage bootstrap/cache +RUN chmod -R 777 /var/www/payment/storage /var/www/payment/bootstrap/cache # Install Composer -COPY --from=docker.arvancloud.ir/composer:latest /usr/bin/composer /usr/bin/composer +COPY --from=composer:latest /usr/bin/composer /usr/bin/composer +ENV COMPOSER_ALLOW_SUPERUSER=1 -CMD composer install;\ - php artisan key:generate;\ - php artisan migrate;\ - php artisan db:seed;\ - frankenphp php-server -r public/ +# Default command +CMD composer install ;\ + php artisan key:generate ;\ + php artisan migrate ;\ + php artisan db:seed ;\ + php-fpm diff --git a/backend/Dockerfile.production b/backend/Dockerfile.production index 848908b5..bfc9a2fe 100644 --- a/backend/Dockerfile.production +++ b/backend/Dockerfile.production @@ -14,13 +14,22 @@ COPY . . RUN composer dump-autoload --optimize --classmap-authoritative -FROM docker.arvancloud.ir/dunglas/frankenphp:1.11.2-php8.5-trixie AS application +FROM docker.arvancloud.ir/php:8.5-fpm-alpine3.23 AS application -RUN apt-get update \ - && apt-get install -y --no-install-recommends libpq-dev unzip zip \ - && install-php-extensions pdo_pgsql pgsql \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* +RUN apk update && apk add --no-cache \ + bash \ + curl \ + libpng \ + libpng-dev \ + libjpeg-turbo-dev \ + freetype-dev \ + libwebp-dev \ + libxml2-dev \ + unzip \ + libzip-dev \ + libpq-dev \ + && docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp \ + && docker-php-ext-install pdo_pgsql pdo_mysql gd zip WORKDIR /var/www/payment @@ -29,4 +38,4 @@ COPY --from=builder /app /var/www/payment RUN chown -R www-data:www-data /var/www/payment \ && chmod -R 775 storage bootstrap/cache -CMD ["frankenphp", "php-server", "-r", "public/"] +CMD ["php-fpm"] diff --git a/docker-compose.production.yml b/docker-compose.production.yml index 1d4536a6..a37a07f1 100644 --- a/docker-compose.production.yml +++ b/docker-compose.production.yml @@ -1,6 +1,26 @@ services: + nginx: + image: docker.arvancloud.ir/nginx:stable-alpine3.23-perl + container_name: nginx + restart: unless-stopped + ports: + - "80:80" + volumes: + - ./nginx/payment.conf:/etc/nginx/conf.d/default.conf + - ${BACKEND_PATH}:/var/www/${PROJECT_NAME} + networks: + - payment + + redis: + image: docker.arvancloud.ir/redis:8.6.0-alpine3.23 + container_name: redis + restart: unless-stopped + tty: true + networks: + - payment + postgres: - image: docker.arvancloud.ir/postgres:17.8-alpine3.23 + image: docker.arvancloud.ir/postgres:18.3-alpine3.23 container_name: pgsql restart: unless-stopped ports: @@ -26,15 +46,8 @@ services: container_name: laravel tty: true restart: unless-stopped - healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:80"] - interval: 30s - timeout: 10s - retries: 3 ports: - - "8003:80" - - "443:443" - - "443:443/udp" + - "80:80" volumes: - ${BACKEND_PATH}:/var/www/${PROJECT_NAME} depends_on: diff --git a/docker-compose.yml b/docker-compose.yml index 2da7a427..ba90b320 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,26 @@ services: + nginx: + image: docker.arvancloud.ir/nginx:stable-alpine3.23-perl + container_name: nginx + restart: unless-stopped + ports: + - "80:80" + volumes: + - ./nginx/payment.conf:/etc/nginx/conf.d/default.conf + - ${BACKEND_PATH}:/var/www/${PROJECT_NAME} + networks: + - payment + + redis: + image: docker.arvancloud.ir/redis:8.6.0-alpine3.23 + container_name: redis + restart: unless-stopped + tty: true + networks: + - payment + postgres: - image: docker.arvancloud.ir/postgres:17.8-alpine3.23 + image: docker.arvancloud.ir/postgres:18.3-alpine3.23 container_name: pgsql restart: unless-stopped ports: @@ -22,13 +42,9 @@ services: tty: true restart: unless-stopped ports: - - "8003:80" - - "443:443" - - "443:443/udp" + - "9000:9000" volumes: - ${BACKEND_PATH}:/var/www/${PROJECT_NAME} -# - caddy_data:/data -# - caddy_config:/config depends_on: - postgres networks: diff --git a/nginx/payment.conf b/nginx/payment.conf new file mode 100644 index 00000000..afcf76bb --- /dev/null +++ b/nginx/payment.conf @@ -0,0 +1,21 @@ +server { + listen 80; + listen [::]:80; + charset utf-8; + + location / { + try_files $uri $uri/ /index.php?$query_string; + } + + location ~ ^/index\.php(/|$) { + include fastcgi_params; + fastcgi_pass laravel:9000; + fastcgi_index index.php; + fastcgi_hide_header X-Powered-By; + fastcgi_param SCRIPT_FILENAME /var/www/payment/backend/public/index.php; + } + + location ~ /\.(?!well-known).* { + deny all; + } +} -- 2.49.1 From 52ed10f831ae529e24d9d2f01fd984ae7e4794bb Mon Sep 17 00:00:00 2001 From: amirghasempoor Date: Mon, 27 Apr 2026 14:35:04 +0330 Subject: [PATCH 02/61] remove production yaml files --- backend/Dockerfile | 35 ++++++++++------- backend/Dockerfile.production | 41 -------------------- docker-compose.production.yml | 73 ----------------------------------- docker-compose.yml | 18 ++++++++- 4 files changed, 37 insertions(+), 130 deletions(-) delete mode 100644 backend/Dockerfile.production delete mode 100644 docker-compose.production.yml diff --git a/backend/Dockerfile b/backend/Dockerfile index e58f4534..4c047139 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,6 +1,21 @@ -FROM docker.arvancloud.ir/php:8.5-fpm-alpine3.23 +FROM docker.arvancloud.ir/composer:latest AS builder + +WORKDIR /app + +COPY composer.json composer.lock ./ + +RUN composer install \ + --no-dev \ + --no-scripts \ + --no-autoloader \ + --prefer-dist + +COPY . . + +RUN composer dump-autoload --optimize --classmap-authoritative + +FROM php:8.5.3-fpm-alpine3.23 AS application -# Install common PHP extension dependencies RUN apk update && apk add --no-cache \ bash \ curl \ @@ -16,19 +31,11 @@ RUN apk update && apk add --no-cache \ && docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp \ && docker-php-ext-install pdo_pgsql pdo_mysql gd zip -# Set the working directory WORKDIR /var/www/payment -COPY . . -RUN chmod -R 777 /var/www/payment/storage /var/www/payment/bootstrap/cache +COPY --from=builder /app /var/www/payment -# Install Composer -COPY --from=composer:latest /usr/bin/composer /usr/bin/composer -ENV COMPOSER_ALLOW_SUPERUSER=1 +RUN chown -R www-data:www-data /var/www/payment \ + && chmod -R 665 storage bootstrap/cache -# Default command -CMD composer install ;\ - php artisan key:generate ;\ - php artisan migrate ;\ - php artisan db:seed ;\ - php-fpm +CMD ["php-fpm"] diff --git a/backend/Dockerfile.production b/backend/Dockerfile.production deleted file mode 100644 index bfc9a2fe..00000000 --- a/backend/Dockerfile.production +++ /dev/null @@ -1,41 +0,0 @@ -FROM docker.arvancloud.ir/composer:latest AS builder - -WORKDIR /app - -COPY composer.json composer.lock ./ - -RUN composer install \ - --no-dev \ - --no-scripts \ - --no-autoloader \ - --prefer-dist - -COPY . . - -RUN composer dump-autoload --optimize --classmap-authoritative - -FROM docker.arvancloud.ir/php:8.5-fpm-alpine3.23 AS application - -RUN apk update && apk add --no-cache \ - bash \ - curl \ - libpng \ - libpng-dev \ - libjpeg-turbo-dev \ - freetype-dev \ - libwebp-dev \ - libxml2-dev \ - unzip \ - libzip-dev \ - libpq-dev \ - && docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp \ - && docker-php-ext-install pdo_pgsql pdo_mysql gd zip - -WORKDIR /var/www/payment - -COPY --from=builder /app /var/www/payment - -RUN chown -R www-data:www-data /var/www/payment \ - && chmod -R 775 storage bootstrap/cache - -CMD ["php-fpm"] diff --git a/docker-compose.production.yml b/docker-compose.production.yml deleted file mode 100644 index a37a07f1..00000000 --- a/docker-compose.production.yml +++ /dev/null @@ -1,73 +0,0 @@ -services: - nginx: - image: docker.arvancloud.ir/nginx:stable-alpine3.23-perl - container_name: nginx - restart: unless-stopped - ports: - - "80:80" - volumes: - - ./nginx/payment.conf:/etc/nginx/conf.d/default.conf - - ${BACKEND_PATH}:/var/www/${PROJECT_NAME} - networks: - - payment - - redis: - image: docker.arvancloud.ir/redis:8.6.0-alpine3.23 - container_name: redis - restart: unless-stopped - tty: true - networks: - - payment - - postgres: - image: docker.arvancloud.ir/postgres:18.3-alpine3.23 - container_name: pgsql - restart: unless-stopped - ports: - - "8091:5432" - environment: - POSTGRES_DB: ${POSTGRES_DB} - POSTGRES_USER: ${POSTGRES_USER} - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} - volumes: - - payment-db:/var/lib/postgresql/data - networks: - - payment - healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"] - interval: 30s - timeout: 10s - retries: 3 - - backend: - build: - context: ${BACKEND_PATH} - dockerfile: Dockerfile.production - container_name: laravel - tty: true - restart: unless-stopped - ports: - - "80:80" - volumes: - - ${BACKEND_PATH}:/var/www/${PROJECT_NAME} - depends_on: - - postgres - networks: - - payment - logging: - driver: "syslog" - options: - syslog-address: "tcp://host.docker.internal:514" - tag: "laravel-backend" - syslog-facility: "daemon" - max-size: "10m" - mem_limit: 512M - cpus: 1 - -volumes: - payment-db: - driver: local - -networks: - payment: - driver: bridge \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index ba90b320..3feb97d6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,7 +4,7 @@ services: container_name: nginx restart: unless-stopped ports: - - "80:80" + - "8000:80" volumes: - ./nginx/payment.conf:/etc/nginx/conf.d/default.conf - ${BACKEND_PATH}:/var/www/${PROJECT_NAME} @@ -20,7 +20,7 @@ services: - payment postgres: - image: docker.arvancloud.ir/postgres:18.3-alpine3.23 + image: postgres:18.2-alpine3.23 container_name: pgsql restart: unless-stopped ports: @@ -29,6 +29,11 @@ services: POSTGRES_DB: ${POSTGRES_DB} POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + healthcheck: + test: [ "CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}" ] + interval: 30s + timeout: 10s + retries: 3 volumes: - payment-db:/var/lib/postgresql/data networks: @@ -49,6 +54,15 @@ services: - postgres networks: - payment +# logging: +# driver: "syslog" +# options: +# syslog-address: "tcp://host.docker.internal:514" +# tag: "laravel-backend" +# syslog-facility: "daemon" +# max-size: "10m" +# mem_limit: 512M +# cpus: 1 volumes: payment-db: -- 2.49.1 From 1dbd816da29a555bbae0d961d3ad0449416a91e7 Mon Sep 17 00:00:00 2001 From: amirghasempoor Date: Mon, 27 Apr 2026 15:18:35 +0330 Subject: [PATCH 03/61] add dockerfile for production --- backend/Dockerfile | 39 +++++++++++++++------------------ backend/Dockerfile.production | 41 +++++++++++++++++++++++++++++++++++ docker-compose.yml | 4 ++-- 3 files changed, 60 insertions(+), 24 deletions(-) create mode 100644 backend/Dockerfile.production diff --git a/backend/Dockerfile b/backend/Dockerfile index 4c047139..7a70c044 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,21 +1,6 @@ -FROM docker.arvancloud.ir/composer:latest AS builder - -WORKDIR /app - -COPY composer.json composer.lock ./ - -RUN composer install \ - --no-dev \ - --no-scripts \ - --no-autoloader \ - --prefer-dist - -COPY . . - -RUN composer dump-autoload --optimize --classmap-authoritative - -FROM php:8.5.3-fpm-alpine3.23 AS application +FROM php:8.5.3-fpm-alpine3.23 +# Install common PHP extension dependencies RUN apk update && apk add --no-cache \ bash \ curl \ @@ -29,13 +14,23 @@ RUN apk update && apk add --no-cache \ libzip-dev \ libpq-dev \ && docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp \ - && docker-php-ext-install pdo_pgsql pdo_mysql gd zip + && docker-php-ext-install pdo_pgsql gd zip +# Set the working directory WORKDIR /var/www/payment -COPY --from=builder /app /var/www/payment +COPY . . -RUN chown -R www-data:www-data /var/www/payment \ - && chmod -R 665 storage bootstrap/cache +RUN chmod -R 665 /var/www/payment/storage /var/www/payment/bootstrap/cache -CMD ["php-fpm"] +# Install Composer +COPY --from=composer:latest /usr/bin/composer /usr/bin/composer +ENV COMPOSER_ALLOW_SUPERUSER=1 + +# Default command +CMD composer install ;\ + composer dump-autoload --optimize --classmap-authoritative ;\ + php artisan key:generate ;\ + php artisan migrate ;\ + php artisan db:seed ;\ + php-fpm diff --git a/backend/Dockerfile.production b/backend/Dockerfile.production new file mode 100644 index 00000000..e2a7d0cf --- /dev/null +++ b/backend/Dockerfile.production @@ -0,0 +1,41 @@ +FROM docker.arvancloud.ir/composer:latest AS builder + +WORKDIR /app + +COPY composer.json composer.lock ./ + +RUN composer install \ + --no-dev \ + --no-scripts \ + --no-autoloader \ + --prefer-dist + +COPY . . + +RUN composer dump-autoload --optimize --classmap-authoritative + +FROM php:8.5.3-fpm-alpine3.23 AS application + +RUN apk update && apk add --no-cache \ + bash \ + curl \ + libpng \ + libpng-dev \ + libjpeg-turbo-dev \ + freetype-dev \ + libwebp-dev \ + libxml2-dev \ + unzip \ + libzip-dev \ + libpq-dev \ + && docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp \ + && docker-php-ext-install pdo_pgsql gd zip + +WORKDIR /var/www/payment + +COPY --from=builder /app /var/www/payment + +RUN chown -R www-data:www-data /var/www/payment \ + && chmod -R 665 storage bootstrap/cache + +CMD ["php-fpm"] diff --git a/docker-compose.yml b/docker-compose.yml index 3feb97d6..899c16e5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,6 @@ services: nginx: - image: docker.arvancloud.ir/nginx:stable-alpine3.23-perl + image: nginx:stable-alpine3.23-perl container_name: nginx restart: unless-stopped ports: @@ -12,7 +12,7 @@ services: - payment redis: - image: docker.arvancloud.ir/redis:8.6.0-alpine3.23 + image: redis:8.6.0-alpine3.23 container_name: redis restart: unless-stopped tty: true -- 2.49.1 From 4596025ba33119ffccddea70801a2106e6b456ba Mon Sep 17 00:00:00 2001 From: faezehzafarbakhsh Date: Mon, 27 Apr 2026 16:12:08 +0330 Subject: [PATCH 04/61] install passport --- backend/app/Models/User.php | 3 +- backend/app/Providers/AppServiceProvider.php | 3 +- backend/bootstrap/app.php | 1 + backend/composer.json | 3 +- backend/composer.lock | 1010 ++++++++++++++++- backend/config/auth.php | 4 + backend/config/passport.php | 48 + ...7_122415_create_oauth_auth_codes_table.php | 39 + ...22416_create_oauth_access_tokens_table.php | 41 + ...2417_create_oauth_refresh_tokens_table.php | 37 + ...4_27_122418_create_oauth_clients_table.php | 42 + ...122419_create_oauth_device_codes_table.php | 42 + backend/routes/api.php | 8 + docker-compose.yml | 4 +- 14 files changed, 1278 insertions(+), 7 deletions(-) create mode 100644 backend/config/passport.php create mode 100644 backend/database/migrations/2026_04_27_122415_create_oauth_auth_codes_table.php create mode 100644 backend/database/migrations/2026_04_27_122416_create_oauth_access_tokens_table.php create mode 100644 backend/database/migrations/2026_04_27_122417_create_oauth_refresh_tokens_table.php create mode 100644 backend/database/migrations/2026_04_27_122418_create_oauth_clients_table.php create mode 100644 backend/database/migrations/2026_04_27_122419_create_oauth_device_codes_table.php create mode 100644 backend/routes/api.php diff --git a/backend/app/Models/User.php b/backend/app/Models/User.php index f6ba1d2e..f62f69bb 100644 --- a/backend/app/Models/User.php +++ b/backend/app/Models/User.php @@ -9,13 +9,14 @@ use Illuminate\Database\Eloquent\Attributes\Hidden; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; +use Laravel\Passport\HasApiTokens; #[Fillable(['name', 'email', 'password'])] #[Hidden(['password', 'remember_token'])] class User extends Authenticatable { /** @use HasFactory */ - use HasFactory, Notifiable; + use HasFactory, Notifiable, HasApiTokens; /** * Get the attributes that should be cast. diff --git a/backend/app/Providers/AppServiceProvider.php b/backend/app/Providers/AppServiceProvider.php index 452e6b65..bbedac67 100644 --- a/backend/app/Providers/AppServiceProvider.php +++ b/backend/app/Providers/AppServiceProvider.php @@ -3,6 +3,7 @@ namespace App\Providers; use Illuminate\Support\ServiceProvider; +use Laravel\Passport\Passport; class AppServiceProvider extends ServiceProvider { @@ -19,6 +20,6 @@ class AppServiceProvider extends ServiceProvider */ public function boot(): void { - // + Passport::enablePasswordGrant(); } } diff --git a/backend/bootstrap/app.php b/backend/bootstrap/app.php index c1832766..c3928c57 100644 --- a/backend/bootstrap/app.php +++ b/backend/bootstrap/app.php @@ -7,6 +7,7 @@ use Illuminate\Foundation\Configuration\Middleware; return Application::configure(basePath: dirname(__DIR__)) ->withRouting( web: __DIR__.'/../routes/web.php', + api: __DIR__.'/../routes/api.php', commands: __DIR__.'/../routes/console.php', health: '/up', ) diff --git a/backend/composer.json b/backend/composer.json index 98ec92e4..d8fdf089 100644 --- a/backend/composer.json +++ b/backend/composer.json @@ -11,6 +11,7 @@ "require": { "php": "^8.3", "laravel/framework": "^13.0", + "laravel/passport": "^13.0", "laravel/tinker": "^3.0" }, "require-dev": { @@ -86,4 +87,4 @@ }, "minimum-stability": "stable", "prefer-stable": true -} \ No newline at end of file +} diff --git a/backend/composer.lock b/backend/composer.lock index 7b2124f5..72005dd0 100644 --- a/backend/composer.lock +++ b/backend/composer.lock @@ -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": "2af3ccbaaf28845c404c1bd165e5a386", + "content-hash": "7298a47753c91eab121f7ef9a163f9f2", "packages": [ { "name": "brick/math", @@ -135,6 +135,73 @@ ], "time": "2024-02-09T16:56:22+00:00" }, + { + "name": "defuse/php-encryption", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/defuse/php-encryption.git", + "reference": "f53396c2d34225064647a05ca76c1da9d99e5828" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/defuse/php-encryption/zipball/f53396c2d34225064647a05ca76c1da9d99e5828", + "reference": "f53396c2d34225064647a05ca76c1da9d99e5828", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "paragonie/random_compat": ">= 2", + "php": ">=5.6.0" + }, + "require-dev": { + "phpunit/phpunit": "^5|^6|^7|^8|^9|^10", + "yoast/phpunit-polyfills": "^2.0.0" + }, + "bin": [ + "bin/generate-defuse-key" + ], + "type": "library", + "autoload": { + "psr-4": { + "Defuse\\Crypto\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Hornby", + "email": "taylor@defuse.ca", + "homepage": "https://defuse.ca/" + }, + { + "name": "Scott Arciszewski", + "email": "info@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "Secure PHP Encryption Library", + "keywords": [ + "aes", + "authenticated encryption", + "cipher", + "crypto", + "cryptography", + "encrypt", + "encryption", + "openssl", + "security", + "symmetric key cryptography" + ], + "support": { + "issues": "https://github.com/defuse/php-encryption/issues", + "source": "https://github.com/defuse/php-encryption/tree/v2.4.0" + }, + "time": "2023-06-19T06:10:36+00:00" + }, { "name": "dflydev/dot-access-data", "version": "v3.0.3", @@ -508,6 +575,70 @@ ], "time": "2025-03-06T22:45:56+00:00" }, + { + "name": "firebase/php-jwt", + "version": "v7.0.5", + "source": { + "type": "git", + "url": "https://github.com/googleapis/php-jwt.git", + "reference": "47ad26bab5e7c70ae8a6f08ed25ff83631121380" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/googleapis/php-jwt/zipball/47ad26bab5e7c70ae8a6f08ed25ff83631121380", + "reference": "47ad26bab5e7c70ae8a6f08ed25ff83631121380", + "shasum": "" + }, + "require": { + "php": "^8.0" + }, + "require-dev": { + "guzzlehttp/guzzle": "^7.4", + "phpfastcache/phpfastcache": "^9.2", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.5", + "psr/cache": "^2.0||^3.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0" + }, + "suggest": { + "ext-sodium": "Support EdDSA (Ed25519) signatures", + "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present" + }, + "type": "library", + "autoload": { + "psr-4": { + "Firebase\\JWT\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Neuman Vong", + "email": "neuman+pear@twilio.com", + "role": "Developer" + }, + { + "name": "Anant Narayanan", + "email": "anant@php.net", + "role": "Developer" + } + ], + "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", + "homepage": "https://github.com/firebase/php-jwt", + "keywords": [ + "jwt", + "php" + ], + "support": { + "issues": "https://github.com/googleapis/php-jwt/issues", + "source": "https://github.com/googleapis/php-jwt/tree/v7.0.5" + }, + "time": "2026-04-01T20:38:03+00:00" + }, { "name": "fruitcake/php-cors", "version": "v1.4.0", @@ -1276,6 +1407,81 @@ }, "time": "2026-04-21T13:32:11+00:00" }, + { + "name": "laravel/passport", + "version": "v13.7.5", + "source": { + "type": "git", + "url": "https://github.com/laravel/passport.git", + "reference": "90053dc4ba681c076855779250109bb624f961f6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/passport/zipball/90053dc4ba681c076855779250109bb624f961f6", + "reference": "90053dc4ba681c076855779250109bb624f961f6", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-openssl": "*", + "firebase/php-jwt": "^6.4|^7.0", + "illuminate/auth": "^11.35|^12.0|^13.0", + "illuminate/console": "^11.35|^12.0|^13.0", + "illuminate/container": "^11.35|^12.0|^13.0", + "illuminate/contracts": "^11.35|^12.0|^13.0", + "illuminate/cookie": "^11.35|^12.0|^13.0", + "illuminate/database": "^11.35|^12.0|^13.0", + "illuminate/encryption": "^11.35|^12.0|^13.0", + "illuminate/http": "^11.35|^12.0|^13.0", + "illuminate/support": "^11.35|^12.0|^13.0", + "league/oauth2-server": "^9.2", + "php": "^8.2", + "php-http/discovery": "^1.20", + "phpseclib/phpseclib": "^3.0", + "psr/http-factory-implementation": "*", + "symfony/console": "^7.1|^8.0", + "symfony/psr-http-message-bridge": "^7.1|^8.0" + }, + "require-dev": { + "orchestra/testbench": "^9.15|^10.8|^11.0", + "phpstan/phpstan": "^2.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Laravel\\Passport\\PassportServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Passport\\": "src/", + "Laravel\\Passport\\Database\\Factories\\": "database/factories/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Laravel Passport provides OAuth2 server support to Laravel.", + "keywords": [ + "laravel", + "oauth", + "passport" + ], + "support": { + "issues": "https://github.com/laravel/passport/issues", + "source": "https://github.com/laravel/passport" + }, + "time": "2026-04-16T14:00:29+00:00" + }, { "name": "laravel/prompts", "version": "v0.3.17", @@ -1465,6 +1671,143 @@ }, "time": "2026-03-17T14:54:13+00:00" }, + { + "name": "lcobucci/clock", + "version": "3.6.0", + "source": { + "type": "git", + "url": "https://github.com/lcobucci/clock.git", + "reference": "4cdd88f761e9be9095ccbedf3e08d61ae216c643" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/lcobucci/clock/zipball/4cdd88f761e9be9095ccbedf3e08d61ae216c643", + "reference": "4cdd88f761e9be9095ccbedf3e08d61ae216c643", + "shasum": "" + }, + "require": { + "php": "~8.4.0 || ~8.5.0", + "psr/clock": "^1.0" + }, + "provide": { + "psr/clock-implementation": "1.0" + }, + "require-dev": { + "infection/infection": "^0.32", + "lcobucci/coding-standard": "^12.0", + "phpstan/extension-installer": "^1.3.1", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-deprecation-rules": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^13.0" + }, + "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/3.6.0" + }, + "funding": [ + { + "url": "https://github.com/lcobucci", + "type": "github" + }, + { + "url": "https://www.patreon.com/lcobucci", + "type": "patreon" + } + ], + "time": "2026-04-13T21:30:16+00:00" + }, + { + "name": "lcobucci/jwt", + "version": "5.6.0", + "source": { + "type": "git", + "url": "https://github.com/lcobucci/jwt.git", + "reference": "bb3e9f21e4196e8afc41def81ef649c164bca25e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/lcobucci/jwt/zipball/bb3e9f21e4196e8afc41def81ef649c164bca25e", + "reference": "bb3e9f21e4196e8afc41def81ef649c164bca25e", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "ext-sodium": "*", + "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", + "psr/clock": "^1.0" + }, + "require-dev": { + "infection/infection": "^0.29", + "lcobucci/clock": "^3.2", + "lcobucci/coding-standard": "^11.0", + "phpbench/phpbench": "^1.2", + "phpstan/extension-installer": "^1.2", + "phpstan/phpstan": "^1.10.7", + "phpstan/phpstan-deprecation-rules": "^1.1.3", + "phpstan/phpstan-phpunit": "^1.3.10", + "phpstan/phpstan-strict-rules": "^1.5.0", + "phpunit/phpunit": "^11.1" + }, + "suggest": { + "lcobucci/clock": ">= 3.2" + }, + "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/5.6.0" + }, + "funding": [ + { + "url": "https://github.com/lcobucci", + "type": "github" + }, + { + "url": "https://www.patreon.com/lcobucci", + "type": "patreon" + } + ], + "time": "2025-10-17T11:30:53+00:00" + }, { "name": "league/commonmark", "version": "2.8.2", @@ -1654,6 +1997,65 @@ ], "time": "2022-12-11T20:36:23+00:00" }, + { + "name": "league/event", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/event.git", + "reference": "ec38ff7ea10cad7d99a79ac937fbcffb9334c210" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/event/zipball/ec38ff7ea10cad7d99a79ac937fbcffb9334c210", + "reference": "ec38ff7ea10cad7d99a79ac937fbcffb9334c210", + "shasum": "" + }, + "require": { + "php": ">=7.2.0", + "psr/event-dispatcher": "^1.0" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.16", + "phpstan/phpstan": "^0.12.45", + "phpunit/phpunit": "^8.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Event\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frenky.net" + } + ], + "description": "Event package", + "keywords": [ + "emitter", + "event", + "listener" + ], + "support": { + "issues": "https://github.com/thephpleague/event/issues", + "source": "https://github.com/thephpleague/event/tree/3.0.3" + }, + "time": "2024-09-04T16:06:53+00:00" + }, { "name": "league/flysystem", "version": "3.33.0", @@ -1842,6 +2244,102 @@ ], "time": "2024-09-21T08:32:55+00:00" }, + { + "name": "league/oauth2-server", + "version": "9.3.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/oauth2-server.git", + "reference": "d8e2f39f645a82b207bbac441694d6e6079357cb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/oauth2-server/zipball/d8e2f39f645a82b207bbac441694d6e6079357cb", + "reference": "d8e2f39f645a82b207bbac441694d6e6079357cb", + "shasum": "" + }, + "require": { + "defuse/php-encryption": "^2.4", + "ext-json": "*", + "ext-openssl": "*", + "lcobucci/clock": "^2.3 || ^3.0", + "lcobucci/jwt": "^5.0", + "league/event": "^3.0", + "league/uri": "^7.0", + "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", + "psr/http-message": "^2.0", + "psr/http-server-middleware": "^1.0" + }, + "replace": { + "league/oauth2server": "*", + "lncd/oauth2": "*" + }, + "require-dev": { + "laminas/laminas-diactoros": "^3.5", + "php-parallel-lint/php-parallel-lint": "^1.3.2", + "phpstan/extension-installer": "^1.3.1", + "phpstan/phpstan": "^1.12|^2.0", + "phpstan/phpstan-deprecation-rules": "^1.1.4|^2.0", + "phpstan/phpstan-phpunit": "^1.3.15|^2.0", + "phpstan/phpstan-strict-rules": "^1.5.2|^2.0", + "phpunit/phpunit": "^10.5|^11.5|^12.0", + "roave/security-advisories": "dev-master", + "slevomat/coding-standard": "^8.14.1", + "squizlabs/php_codesniffer": "^3.8" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\OAuth2\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alex Bilbie", + "email": "hello@alexbilbie.com", + "homepage": "http://www.alexbilbie.com", + "role": "Developer" + }, + { + "name": "Andy Millington", + "email": "andrew@noexceptions.io", + "homepage": "https://www.noexceptions.io", + "role": "Developer" + } + ], + "description": "A lightweight and powerful OAuth 2.0 authorization and resource server library with support for all the core specification grants. This library will allow you to secure your API with OAuth and allow your applications users to approve apps that want to access their data from your API.", + "homepage": "https://oauth2.thephpleague.com/", + "keywords": [ + "Authentication", + "api", + "auth", + "authorisation", + "authorization", + "oauth", + "oauth 2", + "oauth 2.0", + "oauth2", + "protect", + "resource", + "secure", + "server" + ], + "support": { + "issues": "https://github.com/thephpleague/oauth2-server/issues", + "source": "https://github.com/thephpleague/oauth2-server/tree/9.3.0" + }, + "funding": [ + { + "url": "https://github.com/sephster", + "type": "github" + } + ], + "time": "2025-11-25T22:51:15+00:00" + }, { "name": "league/uri", "version": "7.8.1", @@ -2535,6 +3033,204 @@ ], "time": "2026-02-16T23:10:27+00:00" }, + { + "name": "paragonie/constant_time_encoding", + "version": "v3.1.3", + "source": { + "type": "git", + "url": "https://github.com/paragonie/constant_time_encoding.git", + "reference": "d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77", + "reference": "d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77", + "shasum": "" + }, + "require": { + "php": "^8" + }, + "require-dev": { + "infection/infection": "^0", + "nikic/php-fuzzer": "^0", + "phpunit/phpunit": "^9|^10|^11", + "vimeo/psalm": "^4|^5|^6" + }, + "type": "library", + "autoload": { + "psr-4": { + "ParagonIE\\ConstantTime\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com", + "role": "Maintainer" + }, + { + "name": "Steve 'Sc00bz' Thomas", + "email": "steve@tobtu.com", + "homepage": "https://www.tobtu.com", + "role": "Original Developer" + } + ], + "description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)", + "keywords": [ + "base16", + "base32", + "base32_decode", + "base32_encode", + "base64", + "base64_decode", + "base64_encode", + "bin2hex", + "encoding", + "hex", + "hex2bin", + "rfc4648" + ], + "support": { + "email": "info@paragonie.com", + "issues": "https://github.com/paragonie/constant_time_encoding/issues", + "source": "https://github.com/paragonie/constant_time_encoding" + }, + "time": "2025-09-24T15:06:41+00:00" + }, + { + "name": "paragonie/random_compat", + "version": "v9.99.100", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a", + "shasum": "" + }, + "require": { + "php": ">= 7" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*", + "vimeo/psalm": "^1" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "polyfill", + "pseudorandom", + "random" + ], + "support": { + "email": "info@paragonie.com", + "issues": "https://github.com/paragonie/random_compat/issues", + "source": "https://github.com/paragonie/random_compat" + }, + "time": "2020-10-15T08:29:30+00:00" + }, + { + "name": "php-http/discovery", + "version": "1.20.0", + "source": { + "type": "git", + "url": "https://github.com/php-http/discovery.git", + "reference": "82fe4c73ef3363caed49ff8dd1539ba06044910d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/discovery/zipball/82fe4c73ef3363caed49ff8dd1539ba06044910d", + "reference": "82fe4c73ef3363caed49ff8dd1539ba06044910d", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0|^2.0", + "php": "^7.1 || ^8.0" + }, + "conflict": { + "nyholm/psr7": "<1.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "*", + "psr/http-factory-implementation": "*", + "psr/http-message-implementation": "*" + }, + "require-dev": { + "composer/composer": "^1.0.2|^2.0", + "graham-campbell/phpspec-skip-example-extension": "^5.0", + "php-http/httplug": "^1.0 || ^2.0", + "php-http/message-factory": "^1.0", + "phpspec/phpspec": "^5.1 || ^6.1 || ^7.3", + "sebastian/comparator": "^3.0.5 || ^4.0.8", + "symfony/phpunit-bridge": "^6.4.4 || ^7.0.1" + }, + "type": "composer-plugin", + "extra": { + "class": "Http\\Discovery\\Composer\\Plugin", + "plugin-optional": true + }, + "autoload": { + "psr-4": { + "Http\\Discovery\\": "src/" + }, + "exclude-from-classmap": [ + "src/Composer/Plugin.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "Finds and installs PSR-7, PSR-17, PSR-18 and HTTPlug implementations", + "homepage": "http://php-http.org", + "keywords": [ + "adapter", + "client", + "discovery", + "factory", + "http", + "message", + "psr17", + "psr7" + ], + "support": { + "issues": "https://github.com/php-http/discovery/issues", + "source": "https://github.com/php-http/discovery/tree/1.20.0" + }, + "time": "2024-10-02T11:20:13+00:00" + }, { "name": "phpoption/phpoption", "version": "1.9.5", @@ -2610,6 +3306,116 @@ ], "time": "2025-12-27T19:41:33+00:00" }, + { + "name": "phpseclib/phpseclib", + "version": "3.0.52", + "source": { + "type": "git", + "url": "https://github.com/phpseclib/phpseclib.git", + "reference": "2adaefc83df2ec548558307690f376dd7d4f4fce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/2adaefc83df2ec548558307690f376dd7d4f4fce", + "reference": "2adaefc83df2ec548558307690f376dd7d4f4fce", + "shasum": "" + }, + "require": { + "paragonie/constant_time_encoding": "^1|^2|^3", + "paragonie/random_compat": "^1.4|^2.0|^9.99.99", + "php": ">=5.6.1" + }, + "require-dev": { + "phpunit/phpunit": "*" + }, + "suggest": { + "ext-dom": "Install the DOM extension to load XML formatted public keys.", + "ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.", + "ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.", + "ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.", + "ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations." + }, + "type": "library", + "autoload": { + "files": [ + "phpseclib/bootstrap.php" + ], + "psr-4": { + "phpseclib3\\": "phpseclib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jim Wigginton", + "email": "terrafrost@php.net", + "role": "Lead Developer" + }, + { + "name": "Patrick Monnerat", + "email": "pm@datasphere.ch", + "role": "Developer" + }, + { + "name": "Andreas Fischer", + "email": "bantu@phpbb.com", + "role": "Developer" + }, + { + "name": "Hans-Jürgen Petrich", + "email": "petrich@tronic-media.com", + "role": "Developer" + }, + { + "name": "Graham Campbell", + "email": "graham@alt-three.com", + "role": "Developer" + } + ], + "description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.", + "homepage": "http://phpseclib.sourceforge.net", + "keywords": [ + "BigInteger", + "aes", + "asn.1", + "asn1", + "blowfish", + "crypto", + "cryptography", + "encryption", + "rsa", + "security", + "sftp", + "signature", + "signing", + "ssh", + "twofish", + "x.509", + "x509" + ], + "support": { + "issues": "https://github.com/phpseclib/phpseclib/issues", + "source": "https://github.com/phpseclib/phpseclib/tree/3.0.52" + }, + "funding": [ + { + "url": "https://github.com/terrafrost", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpseclib", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpseclib/phpseclib", + "type": "tidelift" + } + ], + "time": "2026-04-27T07:02:15+00:00" + }, { "name": "psr/clock", "version": "1.0.0", @@ -2921,6 +3727,119 @@ }, "time": "2023-04-04T09:54:51+00:00" }, + { + "name": "psr/http-server-handler", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-handler.git", + "reference": "84c4fb66179be4caaf8e97bd239203245302e7d4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/84c4fb66179be4caaf8e97bd239203245302e7d4", + "reference": "84c4fb66179be4caaf8e97bd239203245302e7d4", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side request handler", + "keywords": [ + "handler", + "http", + "http-interop", + "psr", + "psr-15", + "psr-7", + "request", + "response", + "server" + ], + "support": { + "source": "https://github.com/php-fig/http-server-handler/tree/1.0.2" + }, + "time": "2023-04-10T20:06:20+00:00" + }, + { + "name": "psr/http-server-middleware", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-middleware.git", + "reference": "c1481f747daaa6a0782775cd6a8c26a1bf4a3829" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/c1481f747daaa6a0782775cd6a8c26a1bf4a3829", + "reference": "c1481f747daaa6a0782775cd6a8c26a1bf4a3829", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0 || ^2.0", + "psr/http-server-handler": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side middleware", + "keywords": [ + "http", + "http-interop", + "middleware", + "psr", + "psr-15", + "psr-7", + "request", + "response" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-middleware/issues", + "source": "https://github.com/php-fig/http-server-middleware/tree/1.0.2" + }, + "time": "2023-04-11T06:14:47+00:00" + }, { "name": "psr/log", "version": "3.0.2", @@ -5076,6 +5995,93 @@ ], "time": "2026-03-30T15:14:47+00:00" }, + { + "name": "symfony/psr-http-message-bridge", + "version": "v8.0.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/psr-http-message-bridge.git", + "reference": "94facc221260c1d5f20e31ee43cd6c6a824b4a19" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/94facc221260c1d5f20e31ee43cd6c6a824b4a19", + "reference": "94facc221260c1d5f20e31ee43cd6c6a824b4a19", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "psr/http-message": "^1.0|^2.0", + "symfony/http-foundation": "^7.4|^8.0" + }, + "conflict": { + "php-http/discovery": "<1.15" + }, + "require-dev": { + "nyholm/psr7": "^1.1", + "php-http/discovery": "^1.15", + "psr/log": "^1.1.4|^2|^3", + "symfony/browser-kit": "^7.4|^8.0", + "symfony/config": "^7.4|^8.0", + "symfony/event-dispatcher": "^7.4|^8.0", + "symfony/framework-bundle": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/runtime": "^7.4|^8.0" + }, + "type": "symfony-bridge", + "autoload": { + "psr-4": { + "Symfony\\Bridge\\PsrHttpMessage\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "PSR HTTP message bridge", + "homepage": "https://symfony.com", + "keywords": [ + "http", + "http-message", + "psr-17", + "psr-7" + ], + "support": { + "source": "https://github.com/symfony/psr-http-message-bridge/tree/v8.0.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-03-30T15:14:47+00:00" + }, { "name": "symfony/routing", "version": "v8.0.8", @@ -9091,5 +10097,5 @@ "php": "^8.3" }, "platform-dev": {}, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.9.0" } diff --git a/backend/config/auth.php b/backend/config/auth.php index d7568ff1..d0c87999 100644 --- a/backend/config/auth.php +++ b/backend/config/auth.php @@ -42,6 +42,10 @@ return [ 'driver' => 'session', 'provider' => 'users', ], + 'api' => [ + 'driver' => 'passport', + 'provider' => 'users', + ], ], /* diff --git a/backend/config/passport.php b/backend/config/passport.php new file mode 100644 index 00000000..aed43589 --- /dev/null +++ b/backend/config/passport.php @@ -0,0 +1,48 @@ + 'web', + + 'middleware' => [], + + /* + |-------------------------------------------------------------------------- + | Encryption Keys + |-------------------------------------------------------------------------- + | + | Passport uses encryption keys while generating secure access tokens for + | your application. By default, the keys are stored as local files but + | can be set via environment variables when that is more convenient. + | + */ + + 'private_key' => env('PASSPORT_PRIVATE_KEY'), + + 'public_key' => env('PASSPORT_PUBLIC_KEY'), + + /* + |-------------------------------------------------------------------------- + | Passport Database Connection + |-------------------------------------------------------------------------- + | + | By default, Passport's models will utilize your application's default + | database connection. If you wish to use a different connection you + | may specify the configured name of the database connection here. + | + */ + + 'connection' => env('PASSPORT_CONNECTION'), + +]; diff --git a/backend/database/migrations/2026_04_27_122415_create_oauth_auth_codes_table.php b/backend/database/migrations/2026_04_27_122415_create_oauth_auth_codes_table.php new file mode 100644 index 00000000..c700b50e --- /dev/null +++ b/backend/database/migrations/2026_04_27_122415_create_oauth_auth_codes_table.php @@ -0,0 +1,39 @@ +char('id', 80)->primary(); + $table->foreignId('user_id')->index(); + $table->foreignUuid('client_id'); + $table->text('scopes')->nullable(); + $table->boolean('revoked'); + $table->dateTime('expires_at')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('oauth_auth_codes'); + } + + /** + * Get the migration connection name. + */ + public function getConnection(): ?string + { + return $this->connection ?? config('passport.connection'); + } +}; diff --git a/backend/database/migrations/2026_04_27_122416_create_oauth_access_tokens_table.php b/backend/database/migrations/2026_04_27_122416_create_oauth_access_tokens_table.php new file mode 100644 index 00000000..3e50f7f7 --- /dev/null +++ b/backend/database/migrations/2026_04_27_122416_create_oauth_access_tokens_table.php @@ -0,0 +1,41 @@ +char('id', 80)->primary(); + $table->foreignId('user_id')->nullable()->index(); + $table->foreignUuid('client_id'); + $table->string('name')->nullable(); + $table->text('scopes')->nullable(); + $table->boolean('revoked'); + $table->timestamps(); + $table->dateTime('expires_at')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('oauth_access_tokens'); + } + + /** + * Get the migration connection name. + */ + public function getConnection(): ?string + { + return $this->connection ?? config('passport.connection'); + } +}; diff --git a/backend/database/migrations/2026_04_27_122417_create_oauth_refresh_tokens_table.php b/backend/database/migrations/2026_04_27_122417_create_oauth_refresh_tokens_table.php new file mode 100644 index 00000000..afb3c55c --- /dev/null +++ b/backend/database/migrations/2026_04_27_122417_create_oauth_refresh_tokens_table.php @@ -0,0 +1,37 @@ +char('id', 80)->primary(); + $table->char('access_token_id', 80)->index(); + $table->boolean('revoked'); + $table->dateTime('expires_at')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('oauth_refresh_tokens'); + } + + /** + * Get the migration connection name. + */ + public function getConnection(): ?string + { + return $this->connection ?? config('passport.connection'); + } +}; diff --git a/backend/database/migrations/2026_04_27_122418_create_oauth_clients_table.php b/backend/database/migrations/2026_04_27_122418_create_oauth_clients_table.php new file mode 100644 index 00000000..9794dc86 --- /dev/null +++ b/backend/database/migrations/2026_04_27_122418_create_oauth_clients_table.php @@ -0,0 +1,42 @@ +uuid('id')->primary(); + $table->nullableMorphs('owner'); + $table->string('name'); + $table->string('secret')->nullable(); + $table->string('provider')->nullable(); + $table->text('redirect_uris'); + $table->text('grant_types'); + $table->boolean('revoked'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('oauth_clients'); + } + + /** + * Get the migration connection name. + */ + public function getConnection(): ?string + { + return $this->connection ?? config('passport.connection'); + } +}; diff --git a/backend/database/migrations/2026_04_27_122419_create_oauth_device_codes_table.php b/backend/database/migrations/2026_04_27_122419_create_oauth_device_codes_table.php new file mode 100644 index 00000000..ea078319 --- /dev/null +++ b/backend/database/migrations/2026_04_27_122419_create_oauth_device_codes_table.php @@ -0,0 +1,42 @@ +char('id', 80)->primary(); + $table->foreignId('user_id')->nullable()->index(); + $table->foreignUuid('client_id')->index(); + $table->char('user_code', 8)->unique(); + $table->text('scopes'); + $table->boolean('revoked'); + $table->dateTime('user_approved_at')->nullable(); + $table->dateTime('last_polled_at')->nullable(); + $table->dateTime('expires_at')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('oauth_device_codes'); + } + + /** + * Get the migration connection name. + */ + public function getConnection(): ?string + { + return $this->connection ?? config('passport.connection'); + } +}; diff --git a/backend/routes/api.php b/backend/routes/api.php new file mode 100644 index 00000000..f35f6f84 --- /dev/null +++ b/backend/routes/api.php @@ -0,0 +1,8 @@ +user(); +})->middleware('auth:api'); diff --git a/docker-compose.yml b/docker-compose.yml index 899c16e5..d21e5ef4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -21,10 +21,10 @@ services: postgres: image: postgres:18.2-alpine3.23 - container_name: pgsql + container_name: payment-db restart: unless-stopped ports: - - "8091:5432" + - "8092:5432" environment: POSTGRES_DB: ${POSTGRES_DB} POSTGRES_USER: ${POSTGRES_USER} -- 2.49.1 From 7fb9e345a24fc3f5fdc0bbc4a98e4b61c047c8e4 Mon Sep 17 00:00:00 2001 From: amirghasempoor Date: Wed, 29 Apr 2026 14:34:01 +0330 Subject: [PATCH 05/61] create all FTB services --- backend/Dockerfile | 2 +- .../Services/FIB/AuthorizationService.php | 62 ++++++++++++++++ .../Services/FIB/CancelPaymentService.php | 62 ++++++++++++++++ .../FIB/CheckPaymentStatusService.php | 51 +++++++++++++ .../Services/FIB/CreatePaymentService.php | 73 +++++++++++++++++++ .../app/User/Services/FIB/RefundService.php | 62 ++++++++++++++++ backend/bootstrap/cache/.gitignore | 0 backend/storage/app/.gitignore | 0 backend/storage/app/private/.gitignore | 0 backend/storage/app/public/.gitignore | 0 backend/storage/framework/.gitignore | 0 backend/storage/framework/cache/.gitignore | 0 .../storage/framework/cache/data/.gitignore | 0 backend/storage/framework/sessions/.gitignore | 0 backend/storage/framework/testing/.gitignore | 0 backend/storage/framework/views/.gitignore | 0 backend/storage/logs/.gitignore | 0 nginx/payment.conf | 2 +- 18 files changed, 312 insertions(+), 2 deletions(-) create mode 100644 backend/app/User/Services/FIB/AuthorizationService.php create mode 100644 backend/app/User/Services/FIB/CancelPaymentService.php create mode 100644 backend/app/User/Services/FIB/CheckPaymentStatusService.php create mode 100644 backend/app/User/Services/FIB/CreatePaymentService.php create mode 100644 backend/app/User/Services/FIB/RefundService.php mode change 100644 => 100755 backend/bootstrap/cache/.gitignore mode change 100644 => 100755 backend/storage/app/.gitignore mode change 100644 => 100755 backend/storage/app/private/.gitignore mode change 100644 => 100755 backend/storage/app/public/.gitignore mode change 100644 => 100755 backend/storage/framework/.gitignore mode change 100644 => 100755 backend/storage/framework/cache/.gitignore mode change 100644 => 100755 backend/storage/framework/cache/data/.gitignore mode change 100644 => 100755 backend/storage/framework/sessions/.gitignore mode change 100644 => 100755 backend/storage/framework/testing/.gitignore mode change 100644 => 100755 backend/storage/framework/views/.gitignore mode change 100644 => 100755 backend/storage/logs/.gitignore diff --git a/backend/Dockerfile b/backend/Dockerfile index 7a70c044..3a24367a 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -21,7 +21,7 @@ WORKDIR /var/www/payment COPY . . -RUN chmod -R 665 /var/www/payment/storage /var/www/payment/bootstrap/cache +RUN chmod -R 775 /var/www/payment/storage /var/www/payment/bootstrap/cache # Install Composer COPY --from=composer:latest /usr/bin/composer /usr/bin/composer diff --git a/backend/app/User/Services/FIB/AuthorizationService.php b/backend/app/User/Services/FIB/AuthorizationService.php new file mode 100644 index 00000000..03a3ce9e --- /dev/null +++ b/backend/app/User/Services/FIB/AuthorizationService.php @@ -0,0 +1,62 @@ +url = config(''); + $this->channelName = ''; + } + + public function setInputParameters(): void + { + $this->inputParameters = [ + 'client_id' => config('fib.authorization.client_id'), + 'client_secret' => config('fib.authorization.client_secret'), + 'grant_type' => 'client_credentials', + ]; + } + + /** + * @throws Exception + */ + public function run(): array + { + try { + return $this->sendRequest(); + } + catch (Exception $e) { + Log::channel($this->channelName) + ->error(get_class($this), + [ + 'message' => $e->getMessage(), + ] + ); + + throw $e; + } + } + + /** + * @throws ConnectionException + */ + public function sendRequest(): array + { + return Http::withHeaders(['Content-Type' => 'application/json']) + ->throw() + ->withoutVerifying() + ->post($this->url, $this->inputParameters) + ->json(); + } +} diff --git a/backend/app/User/Services/FIB/CancelPaymentService.php b/backend/app/User/Services/FIB/CancelPaymentService.php new file mode 100644 index 00000000..dd46aee0 --- /dev/null +++ b/backend/app/User/Services/FIB/CancelPaymentService.php @@ -0,0 +1,62 @@ +url = config(''); + $this->channelName = ''; + } + + public function setHeaders(): void + { + $this->headers = [ + 'Authorization' => 'Bearer ' . $this->accessToken, + 'Content-Type' => 'application/json', + ]; + } + + /** + * @throws Exception + */ + public function run(): array + { + try { + return $this->sendRequest(); + } + catch (Exception $e) { + Log::channel($this->channelName) + ->error(get_class($this), + [ + 'message' => $e->getMessage(), + ] + ); + + throw $e; + } + } + + /** + * @throws ConnectionException + */ + public function sendRequest(): array + { + return Http::withHeaders($this->headers) + ->throw() + ->withoutVerifying() + ->post($this->url) + ->json(); + } +} diff --git a/backend/app/User/Services/FIB/CheckPaymentStatusService.php b/backend/app/User/Services/FIB/CheckPaymentStatusService.php new file mode 100644 index 00000000..a956dda1 --- /dev/null +++ b/backend/app/User/Services/FIB/CheckPaymentStatusService.php @@ -0,0 +1,51 @@ +url = config(''); + $this->channelName = ''; + } + + /** + * @throws Exception + */ + public function run(): array + { + try { + return $this->sendRequest(); + } + catch (Exception $e) { + Log::channel($this->channelName) + ->error(get_class($this), + [ + 'message' => $e->getMessage(), + ] + ); + + throw $e; + } + } + + /** + * @throws ConnectionException + */ + public function sendRequest(): array + { + return Http::throw() + ->withoutVerifying() + ->post($this->url) + ->json(); + } +} diff --git a/backend/app/User/Services/FIB/CreatePaymentService.php b/backend/app/User/Services/FIB/CreatePaymentService.php new file mode 100644 index 00000000..6b744503 --- /dev/null +++ b/backend/app/User/Services/FIB/CreatePaymentService.php @@ -0,0 +1,73 @@ +url = config(''); + $this->channelName = ''; + } + + public function setAccessToken(string $accessToken): void + { + $this->accessToken = $accessToken; + } + + public function setHeaders(): void + { + $this->headers = [ + 'Authorization' => 'Bearer ' . $this->accessToken, + 'Content-Type' => 'application/json', + ]; + } + + public function setInputParameters(array $inputParameters): void + { + $this->inputParameters = $inputParameters; + } + + /** + * @throws Exception + */ + public function run(): array + { + try { + return $this->sendRequest(); + } + catch (Exception $e) { + Log::channel($this->channelName) + ->error(get_class($this), + [ + 'message' => $e->getMessage(), + ] + ); + + throw $e; + } + } + + /** + * @throws ConnectionException + */ + public function sendRequest(): array + { + return Http::withHeaders($this->headers) + ->throw() + ->withoutVerifying() + ->post($this->url, $this->inputParameters) + ->json(); + } +} diff --git a/backend/app/User/Services/FIB/RefundService.php b/backend/app/User/Services/FIB/RefundService.php new file mode 100644 index 00000000..e0cf197f --- /dev/null +++ b/backend/app/User/Services/FIB/RefundService.php @@ -0,0 +1,62 @@ +url = config(''); + $this->channelName = ''; + } + + public function setHeaders(): void + { + $this->headers = [ + 'Authorization' => 'Bearer ' . $this->accessToken, + 'Content-Type' => 'application/json', + ]; + } + + /** + * @throws Exception + */ + public function run(): array + { + try { + return $this->sendRequest(); + } + catch (Exception $e) { + Log::channel($this->channelName) + ->error(get_class($this), + [ + 'message' => $e->getMessage(), + ] + ); + + throw $e; + } + } + + /** + * @throws ConnectionException + */ + public function sendRequest(): array + { + return Http::withHeaders($this->headers) + ->throw() + ->withoutVerifying() + ->post($this->url) + ->json(); + } +} diff --git a/backend/bootstrap/cache/.gitignore b/backend/bootstrap/cache/.gitignore old mode 100644 new mode 100755 diff --git a/backend/storage/app/.gitignore b/backend/storage/app/.gitignore old mode 100644 new mode 100755 diff --git a/backend/storage/app/private/.gitignore b/backend/storage/app/private/.gitignore old mode 100644 new mode 100755 diff --git a/backend/storage/app/public/.gitignore b/backend/storage/app/public/.gitignore old mode 100644 new mode 100755 diff --git a/backend/storage/framework/.gitignore b/backend/storage/framework/.gitignore old mode 100644 new mode 100755 diff --git a/backend/storage/framework/cache/.gitignore b/backend/storage/framework/cache/.gitignore old mode 100644 new mode 100755 diff --git a/backend/storage/framework/cache/data/.gitignore b/backend/storage/framework/cache/data/.gitignore old mode 100644 new mode 100755 diff --git a/backend/storage/framework/sessions/.gitignore b/backend/storage/framework/sessions/.gitignore old mode 100644 new mode 100755 diff --git a/backend/storage/framework/testing/.gitignore b/backend/storage/framework/testing/.gitignore old mode 100644 new mode 100755 diff --git a/backend/storage/framework/views/.gitignore b/backend/storage/framework/views/.gitignore old mode 100644 new mode 100755 diff --git a/backend/storage/logs/.gitignore b/backend/storage/logs/.gitignore old mode 100644 new mode 100755 diff --git a/nginx/payment.conf b/nginx/payment.conf index afcf76bb..beee1ff4 100644 --- a/nginx/payment.conf +++ b/nginx/payment.conf @@ -12,7 +12,7 @@ server { fastcgi_pass laravel:9000; fastcgi_index index.php; fastcgi_hide_header X-Powered-By; - fastcgi_param SCRIPT_FILENAME /var/www/payment/backend/public/index.php; + fastcgi_param SCRIPT_FILENAME /var/www/payment/public/index.php; } location ~ /\.(?!well-known).* { -- 2.49.1 From 5d2501c71c69453d6d27d339300e342e103d0c5f Mon Sep 17 00:00:00 2001 From: amirghasempoor Date: Wed, 29 Apr 2026 14:42:02 +0330 Subject: [PATCH 06/61] create config file for service --- .../Services/FIB/AuthorizationService.php | 2 +- .../Services/FIB/CancelPaymentService.php | 2 +- .../FIB/CheckPaymentStatusService.php | 2 +- .../Services/FIB/CreatePaymentService.php | 2 +- .../app/User/Services/FIB/RefundService.php | 2 +- backend/config/fib.php | 25 +++++++++++++++++++ 6 files changed, 30 insertions(+), 5 deletions(-) create mode 100644 backend/config/fib.php diff --git a/backend/app/User/Services/FIB/AuthorizationService.php b/backend/app/User/Services/FIB/AuthorizationService.php index 03a3ce9e..6f6a9422 100644 --- a/backend/app/User/Services/FIB/AuthorizationService.php +++ b/backend/app/User/Services/FIB/AuthorizationService.php @@ -15,7 +15,7 @@ class AuthorizationService public function __construct() { - $this->url = config(''); + $this->url = config('fib.authorization.url'); $this->channelName = ''; } diff --git a/backend/app/User/Services/FIB/CancelPaymentService.php b/backend/app/User/Services/FIB/CancelPaymentService.php index dd46aee0..8f8282c8 100644 --- a/backend/app/User/Services/FIB/CancelPaymentService.php +++ b/backend/app/User/Services/FIB/CancelPaymentService.php @@ -16,7 +16,7 @@ class CancelPaymentService public function __construct() { - $this->url = config(''); + $this->url = config('fib.cancelPayment.url'); $this->channelName = ''; } diff --git a/backend/app/User/Services/FIB/CheckPaymentStatusService.php b/backend/app/User/Services/FIB/CheckPaymentStatusService.php index a956dda1..844412c7 100644 --- a/backend/app/User/Services/FIB/CheckPaymentStatusService.php +++ b/backend/app/User/Services/FIB/CheckPaymentStatusService.php @@ -14,7 +14,7 @@ class CheckPaymentStatusService public function __construct() { - $this->url = config(''); + $this->url = config('fib.checkPaymentStatus.url'); $this->channelName = ''; } diff --git a/backend/app/User/Services/FIB/CreatePaymentService.php b/backend/app/User/Services/FIB/CreatePaymentService.php index 6b744503..3b9801b6 100644 --- a/backend/app/User/Services/FIB/CreatePaymentService.php +++ b/backend/app/User/Services/FIB/CreatePaymentService.php @@ -17,7 +17,7 @@ class CreatePaymentService public function __construct() { - $this->url = config(''); + $this->url = config('fib.createPayment.url'); $this->channelName = ''; } diff --git a/backend/app/User/Services/FIB/RefundService.php b/backend/app/User/Services/FIB/RefundService.php index e0cf197f..3a0dfb5b 100644 --- a/backend/app/User/Services/FIB/RefundService.php +++ b/backend/app/User/Services/FIB/RefundService.php @@ -16,7 +16,7 @@ class RefundService public function __construct() { - $this->url = config(''); + $this->url = config('fib.refund.url'); $this->channelName = ''; } diff --git a/backend/config/fib.php b/backend/config/fib.php new file mode 100644 index 00000000..1ddfc3d4 --- /dev/null +++ b/backend/config/fib.php @@ -0,0 +1,25 @@ + [ + 'url' => env('FIB_AUTHORIZATION_URL'), + 'client_id' => env('FIB_CLIENT_ID'), + 'client_secret' => env('FIB_CLIENT_SECRET'), + ], + + 'createPayment' => [ + 'url' => env('FIB_CREATE_PAYMENT_URL'), + ], + + 'checkPaymentStatus' => [ + 'url' => env('FIB_CHECK_PAYMENT_STATUS_URL'), + ], + + 'cancelPayment' => [ + 'url' => env('FIB_CANCEL_PAYMENT_URL'), + ], + + 'refund' => [ + 'url' => env('FIB_REFUND_URL'), + ], +]; -- 2.49.1 From 51aa3b157cf9e92328d6673872909f3afdc0f8e6 Mon Sep 17 00:00:00 2001 From: faezehzafarbakhsh Date: Wed, 29 Apr 2026 16:52:18 +0330 Subject: [PATCH 07/61] create loggin and register whit their form requst --- backend/app/Models/User.php | 1 + .../User/Controller/Auth/AuthController.php | 64 +++++++++++++++++++ .../app/User/Requests/Auth/LoginRequest.php | 30 +++++++++ .../User/Requests/Auth/ًRegisterRequest.php | 30 +++++++++ backend/routes/api.php | 11 ++++ 5 files changed, 136 insertions(+) create mode 100644 backend/app/User/Controller/Auth/AuthController.php create mode 100644 backend/app/User/Requests/Auth/LoginRequest.php create mode 100644 backend/app/User/Requests/Auth/ًRegisterRequest.php diff --git a/backend/app/Models/User.php b/backend/app/Models/User.php index f62f69bb..56fbb341 100644 --- a/backend/app/Models/User.php +++ b/backend/app/Models/User.php @@ -30,4 +30,5 @@ class User extends Authenticatable 'password' => 'hashed', ]; } + protected $guarded = ['id']; } diff --git a/backend/app/User/Controller/Auth/AuthController.php b/backend/app/User/Controller/Auth/AuthController.php new file mode 100644 index 00000000..806a637b --- /dev/null +++ b/backend/app/User/Controller/Auth/AuthController.php @@ -0,0 +1,64 @@ +create([ + 'name' => $request->name, + 'email' => $request->email, + 'password' => Hash::make($request->password), + ]); + + $token = $user->createToken('Register Token')->accessToken; + + return response()->json([ + 'success' => true, + 'token_type' => 'Bearer', + 'access_token' => $token, + 'user' => [ + 'id' => $user->id, + 'username' => $user->username, + ], + ], 201); + } + + public function login(loginRequest $request): JsonResponse + { + if ($request->filled('email')) { + $user = User::query() + ->where('name', $request->name)->first(); + } + elseif ($request->filled('username')) { + $user = User::query() + ->where('name', $request->name) + ->first(); + } + + if (!$user || !Hash::check($request->password, $user->password)) { + return response()->json([ + 'message' => 'Invalid credentials' + ], 401); + } + + $token = $user->createToken('Login Token')->accessToken; + + return response()->json([ + 'success' => true, + 'token' => $token, + 'user' => [ + 'id' => $user->id, + 'name' => $user->name, + ], + ]); + } +} diff --git a/backend/app/User/Requests/Auth/LoginRequest.php b/backend/app/User/Requests/Auth/LoginRequest.php new file mode 100644 index 00000000..0c727045 --- /dev/null +++ b/backend/app/User/Requests/Auth/LoginRequest.php @@ -0,0 +1,30 @@ + + */ + public function rules(): array + { + return [ + 'name' => 'required|exists:users,name', + 'email' => 'required|email|exists:users,email', + 'password' => 'required|string|regex:/^(?=.*[a-zA-Z])(?=.*\d).+$/|min:8', + ]; + } +} diff --git a/backend/app/User/Requests/Auth/ًRegisterRequest.php b/backend/app/User/Requests/Auth/ًRegisterRequest.php new file mode 100644 index 00000000..b549fc37 --- /dev/null +++ b/backend/app/User/Requests/Auth/ًRegisterRequest.php @@ -0,0 +1,30 @@ + + */ + public function rules(): array + { + return [ + 'name' => 'required_without:email|unique:users,name', + 'email' => 'required|email|unique:users,email', + 'password' => 'required|string|regex:/^(?=.*[a-zA-Z])(?=.*\d).+$/|min:8', + ]; + } +} diff --git a/backend/routes/api.php b/backend/routes/api.php index f35f6f84..608543fc 100644 --- a/backend/routes/api.php +++ b/backend/routes/api.php @@ -1,8 +1,19 @@ user(); })->middleware('auth:api'); + +Route::prefix('user')->group(function () { + Route::prefix('Auth') + ->namespace('Auth') + ->controller(AuthController::class) + ->group(function () { + Route::post('register', 'register')->name('register'); + Route::post('login', 'login')->name('login'); + }); +}); -- 2.49.1 From 5b42e23afcb05b4be9519b6f0c778d573dee717e Mon Sep 17 00:00:00 2001 From: faezehzafarbakhsh Date: Sat, 2 May 2026 09:09:34 +0330 Subject: [PATCH 08/61] improve form requst --- backend/app/User/Requests/Auth/LoginRequest.php | 4 ++-- backend/app/User/Requests/Auth/ًRegisterRequest.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/app/User/Requests/Auth/LoginRequest.php b/backend/app/User/Requests/Auth/LoginRequest.php index 0c727045..1b92f793 100644 --- a/backend/app/User/Requests/Auth/LoginRequest.php +++ b/backend/app/User/Requests/Auth/LoginRequest.php @@ -22,8 +22,8 @@ class LoginRequest extends FormRequest public function rules(): array { return [ - 'name' => 'required|exists:users,name', - 'email' => 'required|email|exists:users,email', + 'name' => 'required_without:email|exists:users,name', + 'email' => 'required_without:name|email|exists:users,email', 'password' => 'required|string|regex:/^(?=.*[a-zA-Z])(?=.*\d).+$/|min:8', ]; } diff --git a/backend/app/User/Requests/Auth/ًRegisterRequest.php b/backend/app/User/Requests/Auth/ًRegisterRequest.php index b549fc37..e26d14e1 100644 --- a/backend/app/User/Requests/Auth/ًRegisterRequest.php +++ b/backend/app/User/Requests/Auth/ًRegisterRequest.php @@ -23,7 +23,7 @@ class ًRegisterRequest extends FormRequest { return [ 'name' => 'required_without:email|unique:users,name', - 'email' => 'required|email|unique:users,email', + 'email' => 'required_without:name|email|unique:users,email', 'password' => 'required|string|regex:/^(?=.*[a-zA-Z])(?=.*\d).+$/|min:8', ]; } -- 2.49.1 From e74619d0ab86ac0fa0356e0c3d8276f7aaee5c6e Mon Sep 17 00:00:00 2001 From: faezehzafarbakhsh Date: Sat, 2 May 2026 15:29:09 +0330 Subject: [PATCH 09/61] reviwe my form request in login --- backend/app/User/Requests/Auth/LoginRequest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/app/User/Requests/Auth/LoginRequest.php b/backend/app/User/Requests/Auth/LoginRequest.php index 1b92f793..d00d7154 100644 --- a/backend/app/User/Requests/Auth/LoginRequest.php +++ b/backend/app/User/Requests/Auth/LoginRequest.php @@ -22,9 +22,9 @@ class LoginRequest extends FormRequest public function rules(): array { return [ - 'name' => 'required_without:email|exists:users,name', - 'email' => 'required_without:name|email|exists:users,email', - 'password' => 'required|string|regex:/^(?=.*[a-zA-Z])(?=.*\d).+$/|min:8', + 'name' => 'required_without:email|string', + 'email' => 'required_without:name|email', + 'password' => 'required|string', ]; } } -- 2.49.1 From f788f9a7eabccb90ae88b1b49c1545668083d092 Mon Sep 17 00:00:00 2001 From: faezehzafarbakhsh Date: Sat, 2 May 2026 17:07:40 +0330 Subject: [PATCH 10/61] improve code and fix their bugs --- backend/app/Traits/ApiResponse.php | 31 +++++++++++ .../User/Controller/Auth/AuthController.php | 52 ++++++++++--------- .../app/User/Requests/Auth/LoginRequest.php | 2 +- ...RegisterRequest.php => RegisterRequest.php} | 4 +- backend/routes/api.php | 3 +- 5 files changed, 63 insertions(+), 29 deletions(-) create mode 100644 backend/app/Traits/ApiResponse.php rename backend/app/User/Requests/Auth/{ًRegisterRequest.php => RegisterRequest.php} (84%) diff --git a/backend/app/Traits/ApiResponse.php b/backend/app/Traits/ApiResponse.php new file mode 100644 index 00000000..5c9997d8 --- /dev/null +++ b/backend/app/Traits/ApiResponse.php @@ -0,0 +1,31 @@ +json([ + 'data' => $data + ], $statusCode); + } + + $message = $message ?? __('messages.successful'); + + return response()->json([ + 'message' => $message + ], $statusCode); + } + + public function errorResponse(string $message, string $type = 'logical_exception', int $statusCode = 422): JsonResponse + { + return response()->json([ + 'type' => $type, + 'message' => $message + ], $statusCode); + } +} diff --git a/backend/app/User/Controller/Auth/AuthController.php b/backend/app/User/Controller/Auth/AuthController.php index 806a637b..49640cb4 100644 --- a/backend/app/User/Controller/Auth/AuthController.php +++ b/backend/app/User/Controller/Auth/AuthController.php @@ -4,61 +4,63 @@ namespace App\User\Controller\Auth; use App\Http\Controllers\Controller; use App\Models\User; +use App\Traits\ApiResponse; use App\User\Requests\Auth\LoginRequest; -use App\User\Requests\Auth\ًRegisterRequest; +use App\User\Requests\Auth\RegisterRequest; use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; use Illuminate\Support\Facades\Hash; class AuthController extends Controller { - public function register(ًRegisterRequest $request): JsonResponse + use ApiResponse; + + public function register(RegisterRequest $request): JsonResponse { $user = User::query()->create([ - 'name' => $request->name, + 'username' => $request->username, 'email' => $request->email, 'password' => Hash::make($request->password), ]); $token = $user->createToken('Register Token')->accessToken; - return response()->json([ - 'success' => true, + return $this->successResponse([ 'token_type' => 'Bearer', 'access_token' => $token, - 'user' => [ - 'id' => $user->id, - 'username' => $user->username, - ], - ], 201); + ]); } - public function login(loginRequest $request): JsonResponse + public function login(LoginRequest $request): JsonResponse { + $user = null; + if ($request->filled('email')) { $user = User::query() - ->where('name', $request->name)->first(); - } - elseif ($request->filled('username')) { + ->where('email', $request->email) + ->first(); + } elseif ($request->filled('username')) { $user = User::query() - ->where('name', $request->name) + ->where('username', $request->username) ->first(); } if (!$user || !Hash::check($request->password, $user->password)) { - return response()->json([ - 'message' => 'Invalid credentials' - ], 401); + return $this->errorResponse(''); } $token = $user->createToken('Login Token')->accessToken; - return response()->json([ - 'success' => true, - 'token' => $token, - 'user' => [ - 'id' => $user->id, - 'name' => $user->name, - ], + return $this->successResponse([ + 'token_type' => 'Bearer', + 'access_token' => $token, ]); } + + public function logout(Request $request): JsonResponse + { + $request->user()->token()->revoke(); + + return $this->successResponse(); + } } diff --git a/backend/app/User/Requests/Auth/LoginRequest.php b/backend/app/User/Requests/Auth/LoginRequest.php index d00d7154..18442f03 100644 --- a/backend/app/User/Requests/Auth/LoginRequest.php +++ b/backend/app/User/Requests/Auth/LoginRequest.php @@ -22,7 +22,7 @@ class LoginRequest extends FormRequest public function rules(): array { return [ - 'name' => 'required_without:email|string', + 'username' => 'required_without:email|string', 'email' => 'required_without:name|email', 'password' => 'required|string', ]; diff --git a/backend/app/User/Requests/Auth/ًRegisterRequest.php b/backend/app/User/Requests/Auth/RegisterRequest.php similarity index 84% rename from backend/app/User/Requests/Auth/ًRegisterRequest.php rename to backend/app/User/Requests/Auth/RegisterRequest.php index e26d14e1..54f8c783 100644 --- a/backend/app/User/Requests/Auth/ًRegisterRequest.php +++ b/backend/app/User/Requests/Auth/RegisterRequest.php @@ -4,7 +4,7 @@ namespace App\User\Requests\Auth; use Illuminate\Foundation\Http\FormRequest; -class ًRegisterRequest extends FormRequest +class RegisterRequest extends FormRequest { /** * Determine if the user is authorized to make this request. @@ -22,7 +22,7 @@ class ًRegisterRequest extends FormRequest public function rules(): array { return [ - 'name' => 'required_without:email|unique:users,name', + 'username' => 'required_without:email|unique:users,name', 'email' => 'required_without:name|email|unique:users,email', 'password' => 'required|string|regex:/^(?=.*[a-zA-Z])(?=.*\d).+$/|min:8', ]; diff --git a/backend/routes/api.php b/backend/routes/api.php index 608543fc..fa658a85 100644 --- a/backend/routes/api.php +++ b/backend/routes/api.php @@ -10,10 +10,11 @@ Route::get('/user', function (Request $request) { Route::prefix('user')->group(function () { Route::prefix('Auth') - ->namespace('Auth') + ->name('Auth') ->controller(AuthController::class) ->group(function () { Route::post('register', 'register')->name('register'); Route::post('login', 'login')->name('login'); + Route::post('logout', 'logout')->middleware('auth:api')->name('logout'); }); }); -- 2.49.1 From 0bba710b7d2c27d1ebf9b675efeae48d39152c09 Mon Sep 17 00:00:00 2001 From: faezehzafarbakhsh Date: Sun, 3 May 2026 15:11:29 +0330 Subject: [PATCH 11/61] fix code --- backend/app/Providers/AppServiceProvider.php | 2 ++ backend/app/User/Controller/Auth/AuthController.php | 7 +++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/backend/app/Providers/AppServiceProvider.php b/backend/app/Providers/AppServiceProvider.php index bbedac67..616a7aea 100644 --- a/backend/app/Providers/AppServiceProvider.php +++ b/backend/app/Providers/AppServiceProvider.php @@ -4,6 +4,7 @@ namespace App\Providers; use Illuminate\Support\ServiceProvider; use Laravel\Passport\Passport; +use Carbon\CarbonInterval; class AppServiceProvider extends ServiceProvider { @@ -21,5 +22,6 @@ class AppServiceProvider extends ServiceProvider public function boot(): void { Passport::enablePasswordGrant(); + Passport::personalAccessTokensExpireIn(CarbonInterval::hour(2)); } } diff --git a/backend/app/User/Controller/Auth/AuthController.php b/backend/app/User/Controller/Auth/AuthController.php index 49640cb4..0d09f03f 100644 --- a/backend/app/User/Controller/Auth/AuthController.php +++ b/backend/app/User/Controller/Auth/AuthController.php @@ -9,6 +9,7 @@ use App\User\Requests\Auth\LoginRequest; use App\User\Requests\Auth\RegisterRequest; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Hash; class AuthController extends Controller @@ -26,8 +27,7 @@ class AuthController extends Controller $token = $user->createToken('Register Token')->accessToken; return $this->successResponse([ - 'token_type' => 'Bearer', - 'access_token' => $token, + 'token' => $token, ]); } @@ -52,8 +52,7 @@ class AuthController extends Controller $token = $user->createToken('Login Token')->accessToken; return $this->successResponse([ - 'token_type' => 'Bearer', - 'access_token' => $token, + 'token' => $token, ]); } -- 2.49.1 From 5024a242d290cd1f1b3d02e447eb931516694f65 Mon Sep 17 00:00:00 2001 From: faezehzafarbakhsh Date: Sun, 3 May 2026 15:28:50 +0330 Subject: [PATCH 12/61] fix code --- .../app/User/Controller/Auth/AuthController.php | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/backend/app/User/Controller/Auth/AuthController.php b/backend/app/User/Controller/Auth/AuthController.php index 0d09f03f..4dcabe15 100644 --- a/backend/app/User/Controller/Auth/AuthController.php +++ b/backend/app/User/Controller/Auth/AuthController.php @@ -33,17 +33,12 @@ class AuthController extends Controller public function login(LoginRequest $request): JsonResponse { - $user = null; + $uniq_identifier = $request->input('login'); + $user = User::query() + ->where('email', $uniq_identifier) + ->orWhere('username', $uniq_identifier) + ->first(); - if ($request->filled('email')) { - $user = User::query() - ->where('email', $request->email) - ->first(); - } elseif ($request->filled('username')) { - $user = User::query() - ->where('username', $request->username) - ->first(); - } if (!$user || !Hash::check($request->password, $user->password)) { return $this->errorResponse(''); -- 2.49.1 From cee480fff5e9e02ff1a4cae2774feb34101062c8 Mon Sep 17 00:00:00 2001 From: faezehzafarbakhsh Date: Sun, 3 May 2026 15:29:57 +0330 Subject: [PATCH 13/61] improve the code in some part of the project --- backend/app/User/Controller/Auth/AuthController.php | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/app/User/Controller/Auth/AuthController.php b/backend/app/User/Controller/Auth/AuthController.php index 4dcabe15..d6ca266d 100644 --- a/backend/app/User/Controller/Auth/AuthController.php +++ b/backend/app/User/Controller/Auth/AuthController.php @@ -9,7 +9,6 @@ use App\User\Requests\Auth\LoginRequest; use App\User\Requests\Auth\RegisterRequest; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; -use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Hash; class AuthController extends Controller -- 2.49.1 From 264ed1ecb9b7054e4a79f63663fd55d965557f21 Mon Sep 17 00:00:00 2001 From: amirghasempoor Date: Mon, 4 May 2026 14:52:54 +0330 Subject: [PATCH 14/61] install data table --- backend/app/Facades/DataTable/DataTable.php | 67 +++++++++ .../app/Facades/DataTable/DataTableFacade.php | 13 ++ .../Providers/DataTableServiceProvider.php | 27 ++++ .../app/Services/DataTable/DataTableInput.php | 83 ++++++++++++ .../Services/DataTable/DataTableService.php | 128 ++++++++++++++++++ .../app/Services/DataTable/Enums/DataType.php | 15 ++ .../Services/DataTable/Enums/SearchType.php | 19 +++ .../Exceptions/InvalidFilterException.php | 21 +++ .../Exceptions/InvalidParameterInterface.php | 7 + .../Exceptions/InvalidRelationException.php | 21 +++ .../Exceptions/InvalidSortingException.php | 21 +++ .../Services/DataTable/Filter/ApplyFilter.php | 84 ++++++++++++ .../app/Services/DataTable/Filter/Filter.php | 73 ++++++++++ .../Filter/SearchFunctions/FilterBetween.php | 33 +++++ .../Filter/SearchFunctions/FilterContains.php | 31 +++++ .../Filter/SearchFunctions/FilterEquals.php | 30 ++++ .../SearchFunctions/FilterGreaterThan.php | 18 +++ .../FilterGreaterThanOrEqual.php | 18 +++ .../Filter/SearchFunctions/FilterLessThan.php | 18 +++ .../SearchFunctions/FilterLessThanOrEqual.php | 18 +++ .../SearchFunctions/FilterNotEquals.php | 17 +++ .../Filter/SearchFunctions/SearchFilter.php | 22 +++ .../app/Services/DataTable/Sort/ApplySort.php | 31 +++++ backend/app/Services/DataTable/Sort/Sort.php | 39 ++++++ .../DataTable/Validators/FilterValidator.php | 69 ++++++++++ .../Validators/RelationValidator.php | 25 ++++ .../DataTable/Validators/SortingValidator.php | 39 ++++++ backend/bootstrap/providers.php | 5 +- 28 files changed, 989 insertions(+), 3 deletions(-) create mode 100644 backend/app/Facades/DataTable/DataTable.php create mode 100644 backend/app/Facades/DataTable/DataTableFacade.php create mode 100644 backend/app/Providers/DataTableServiceProvider.php create mode 100644 backend/app/Services/DataTable/DataTableInput.php create mode 100644 backend/app/Services/DataTable/DataTableService.php create mode 100644 backend/app/Services/DataTable/Enums/DataType.php create mode 100644 backend/app/Services/DataTable/Enums/SearchType.php create mode 100644 backend/app/Services/DataTable/Exceptions/InvalidFilterException.php create mode 100644 backend/app/Services/DataTable/Exceptions/InvalidParameterInterface.php create mode 100644 backend/app/Services/DataTable/Exceptions/InvalidRelationException.php create mode 100644 backend/app/Services/DataTable/Exceptions/InvalidSortingException.php create mode 100644 backend/app/Services/DataTable/Filter/ApplyFilter.php create mode 100644 backend/app/Services/DataTable/Filter/Filter.php create mode 100644 backend/app/Services/DataTable/Filter/SearchFunctions/FilterBetween.php create mode 100644 backend/app/Services/DataTable/Filter/SearchFunctions/FilterContains.php create mode 100644 backend/app/Services/DataTable/Filter/SearchFunctions/FilterEquals.php create mode 100644 backend/app/Services/DataTable/Filter/SearchFunctions/FilterGreaterThan.php create mode 100644 backend/app/Services/DataTable/Filter/SearchFunctions/FilterGreaterThanOrEqual.php create mode 100644 backend/app/Services/DataTable/Filter/SearchFunctions/FilterLessThan.php create mode 100644 backend/app/Services/DataTable/Filter/SearchFunctions/FilterLessThanOrEqual.php create mode 100644 backend/app/Services/DataTable/Filter/SearchFunctions/FilterNotEquals.php create mode 100644 backend/app/Services/DataTable/Filter/SearchFunctions/SearchFilter.php create mode 100644 backend/app/Services/DataTable/Sort/ApplySort.php create mode 100644 backend/app/Services/DataTable/Sort/Sort.php create mode 100644 backend/app/Services/DataTable/Validators/FilterValidator.php create mode 100644 backend/app/Services/DataTable/Validators/RelationValidator.php create mode 100644 backend/app/Services/DataTable/Validators/SortingValidator.php diff --git a/backend/app/Facades/DataTable/DataTable.php b/backend/app/Facades/DataTable/DataTable.php new file mode 100644 index 00000000..63699505 --- /dev/null +++ b/backend/app/Facades/DataTable/DataTable.php @@ -0,0 +1,67 @@ +filters); + $sorting = json_decode($request->sorting); + $rels = $request->rels ?: array(); + + $dataTableInput = new DataTableInput( + $request->start, + $request->size, + $filters, + $sorting, + $rels, + $allowedFilters, + $allowedSortings, + $allowedGroupBy + ); + + $query = $this->makeQueryFromModel($mixed); + + $dataTableService = (new DataTableService($query, $dataTableInput)) + ->setAllowedFilters($allowedFilters) + ->setAllowedRelations($allowedRelations) + ->setAllowedSortings($allowedSortings) + ->setAllowedSelects($allowedSelects) + ->setAllowedGroupBy($allowedGroupBy); + + return $dataTableService->getData(); + } + + protected function makeQueryFromModel(Model|Builder $mixed): Builder + { + return ($mixed instanceof Model) ? $mixed->query() : $mixed; + } +} diff --git a/backend/app/Facades/DataTable/DataTableFacade.php b/backend/app/Facades/DataTable/DataTableFacade.php new file mode 100644 index 00000000..98f2ac64 --- /dev/null +++ b/backend/app/Facades/DataTable/DataTableFacade.php @@ -0,0 +1,13 @@ +app->bind('datatable', function () { + return new DataTable(); + }); + } + + /** + * Bootstrap services. + */ + public function boot(): void + { + // + } +} diff --git a/backend/app/Services/DataTable/DataTableInput.php b/backend/app/Services/DataTable/DataTableInput.php new file mode 100644 index 00000000..1bbe5200 --- /dev/null +++ b/backend/app/Services/DataTable/DataTableInput.php @@ -0,0 +1,83 @@ +start; + } + + public function getSize(): ?int + { + return $this->size; + } + + /** + * @return array returns an array of Filter objects + */ + public function getFilters(): array + { + $filters = array(); + + foreach ($this->filters as $filter) { + $filters[] = new Filter( + $filter->id, + $filter->value, + $filter->fn, + $filter->datatype, + $this->allowedFilters + ); + } + + return $filters; + } + + /** + * @return array + */ + public function getSorting(): ?array + { + $sorts = []; + if (!empty($this->sorting)){ + foreach ($this->sorting as $sort) { + $sorts[] = new Sort($sort->id, $sort->desc, $this->allowedSortings); + } + } + return $sorts; + } + + public function getRelations(): array + { + return $this->rels; + } + + public function getGroupBy(): ?array + { + return $this->GroupBy; + } +} diff --git a/backend/app/Services/DataTable/DataTableService.php b/backend/app/Services/DataTable/DataTableService.php new file mode 100644 index 00000000..5755ad1d --- /dev/null +++ b/backend/app/Services/DataTable/DataTableService.php @@ -0,0 +1,128 @@ +allowedFilters = $allowedFilters; + return $this; + } + + public function setAllowedRelations(array $allowedRelations): DataTableService + { + $this->allowedRelations = $allowedRelations; + return $this; + } + + public function setAllowedSortings(array $allowedSortings): DataTableService + { + $this->allowedSortings = $allowedSortings; + return $this; + } + + public function setAllowedSelects(array $allowedSelects): DataTableService + { + $this->allowedSelects = $allowedSelects; + return $this; + } + + public function setAllowedGroupBy(array $allowedGroupBy): DataTableService + { + $this->allowedGroupBy = $allowedGroupBy; + return $this; + } + + /** + * Handle 'getData' operations + * @return array + */ + public function getData(): array + { + $query = $this->buildQuery(); + $data = $query->get(); + + return array( + 'data' => $data, + 'meta' => [ + 'totalRowCount' => $this->totalRowCount + ] + ); + } + + protected function buildQuery(): Builder + { + $query = $this->query; + + foreach ($this->dataTableInput->getFilters() as $filter) { + $query = (new ApplyFilter($query, $filter))->apply(); + } + + $query = $this->applySelect($query, $this->allowedSelects); + $query = $this->includeRelationsInQuery($query, $this->allowedRelations); + $query = $this->applyGroupBy($query, $this->allowedGroupBy); + + $this->totalRowCount = $query->count(); + + if (!is_null($this->dataTableInput->getStart())) { + $query->offset($this->dataTableInput->getStart()); + } + + if(!is_null($this->dataTableInput->getSize())){ + $query->limit($this->dataTableInput->getSize()); + } + + $sorting = $this->dataTableInput->getSorting(); + foreach ($sorting as $sort){ + $query = (new ApplySort($query, $sort))->apply(); + } + + return $query; + } + + protected function applySelect(Builder $query, array $selectedFields): Builder + { + if (!empty($selectedFields)) { + $query->select($selectedFields); + } + + return $query; + } + + protected function applyGroupBy(Builder $query, array $groupBy): Builder + { + return empty($groupBy) ? $query : $query->groupBy($groupBy); + } + + protected function includeRelationsInQuery(Builder $query, array $rels): Builder + { + if (!empty($rels)) { + $query->with($rels); + } + + return $query; + } + + // (later) define mapping of relation names to prevent relation name expose. + // (later) define mapping of column names to prevent column name expose. +} diff --git a/backend/app/Services/DataTable/Enums/DataType.php b/backend/app/Services/DataTable/Enums/DataType.php new file mode 100644 index 00000000..e16e98cd --- /dev/null +++ b/backend/app/Services/DataTable/Enums/DataType.php @@ -0,0 +1,15 @@ +fieldName = $fieldName; + parent::__construct($message, $code, $previous); + } + + public function getFieldName() + { + return $this->fieldName; + } +} diff --git a/backend/app/Services/DataTable/Exceptions/InvalidParameterInterface.php b/backend/app/Services/DataTable/Exceptions/InvalidParameterInterface.php new file mode 100644 index 00000000..3e7b3c73 --- /dev/null +++ b/backend/app/Services/DataTable/Exceptions/InvalidParameterInterface.php @@ -0,0 +1,7 @@ +fieldName = $fieldName; + parent::__construct($message, $code, $previous); + } + + public function getFieldName() + { + return $this->fieldName; + } +} diff --git a/backend/app/Services/DataTable/Exceptions/InvalidSortingException.php b/backend/app/Services/DataTable/Exceptions/InvalidSortingException.php new file mode 100644 index 00000000..d1704699 --- /dev/null +++ b/backend/app/Services/DataTable/Exceptions/InvalidSortingException.php @@ -0,0 +1,21 @@ +fieldName = $fieldName; + parent::__construct($message, $code, $previous); + } + + public function getFieldName() + { + return $this->fieldName; + } +} diff --git a/backend/app/Services/DataTable/Filter/ApplyFilter.php b/backend/app/Services/DataTable/Filter/ApplyFilter.php new file mode 100644 index 00000000..60ac88ea --- /dev/null +++ b/backend/app/Services/DataTable/Filter/ApplyFilter.php @@ -0,0 +1,84 @@ +filter; + $query = $this->query; + + $searchType = SearchType::from($filter->getFn()); + switch ($searchType) { + case SearchType::CONTAINS: + $this->searchFilter = new FilterContains($query, $filter); + break; + + case SearchType::EQUALS: + $this->searchFilter = new FilterEquals($query, $filter); + break; + + case SearchType::NOT_EQUALS: + $this->searchFilter = new FilterNotEquals($query, $filter); + break; + + case SearchType::BETWEEN: + $this->searchFilter = new FilterBetween($query, $filter); + break; + + case SearchType::GREATER_THAN: + $this->searchFilter = new FilterGreaterThan($query, $filter); + break; + + case SearchType::LESS_THAN: + $this->searchFilter = new FilterLessThan($query, $filter); + break; + + default: + $searchFunction = $filter->getFn(); + throw new InvalidFilterException($searchFunction, "search function `$searchFunction` is invalid."); + + } + + $relation = $this->filter->getRelation(); + return method_exists($this->query->getModel(), $relation) ? $this->applyFilterToRelation($relation) : $this->searchFilter->apply(); + } + + protected function applyFilterToRelation(string $relation): Builder + { + return $this->query->whereHas($relation, function (Builder $query) { + $this->filter->removeRelationFromId(); + $this->applyFilter($query, $this->filter); + }); + } + + private function applyFilter(Builder $query, Filter $filter): Builder + { + return (new ApplyFilter($query, $filter))->apply(); + } +} diff --git a/backend/app/Services/DataTable/Filter/Filter.php b/backend/app/Services/DataTable/Filter/Filter.php new file mode 100644 index 00000000..22362a1d --- /dev/null +++ b/backend/app/Services/DataTable/Filter/Filter.php @@ -0,0 +1,73 @@ +isValid($this, $this->allowedFilters); + } + + public function getId(): string + { + return $this->id; + } + + public function getValue(): array|int|string + { + return $this->value; + } + + public function getFn(): string + { + return $this->fn; + } + + public function getDatatype(): string + { + return $this->datatype; + } + + public function getRelation(): string + { + $fieldArray = explode('.', $this->id); + return count($fieldArray) > 1 ? $fieldArray[0] : ''; + } + + public function getColumn(): string + { + $fieldArray = explode('.', $this->id); + return array_pop($fieldArray); + } + + public function removeRelationFromId(): void + { + $this->id = $this->getColumn(); + } + + public function setValue(int|array|string $value): void + { + $this->value = $value; + } + +} diff --git a/backend/app/Services/DataTable/Filter/SearchFunctions/FilterBetween.php b/backend/app/Services/DataTable/Filter/SearchFunctions/FilterBetween.php new file mode 100644 index 00000000..7a6292da --- /dev/null +++ b/backend/app/Services/DataTable/Filter/SearchFunctions/FilterBetween.php @@ -0,0 +1,33 @@ +query; + [$minVal, $maxVal] = $this->filter->getValue(); + + if ($minVal) { + if ($this->filter->getDatatype() == "date") { + $minVal = $minVal . " 00:00:00"; + } + $this->filter->setValue($minVal); + $query = (new FilterGreaterThanOrEqual($this->query, $this->filter))->apply(); + } + + if ($maxVal) { + if ($this->filter->getDatatype() == "date") { + $maxVal = $maxVal . " 23:59:59"; + } + $this->filter->setValue($maxVal); + $query = (new FilterLessThanOrEqual($this->query, $this->filter))->apply(); + } + + return $query; + } +} diff --git a/backend/app/Services/DataTable/Filter/SearchFunctions/FilterContains.php b/backend/app/Services/DataTable/Filter/SearchFunctions/FilterContains.php new file mode 100644 index 00000000..686fe9f1 --- /dev/null +++ b/backend/app/Services/DataTable/Filter/SearchFunctions/FilterContains.php @@ -0,0 +1,31 @@ +filter->getId(); + $value = '%' . $this->filter->getValue() . '%'; + + if ($this->filter->getDatatype() == DataType::TEXT->value) { + $query = $this->searchIgnoreCase($column, $value); + + } else { + $query = $this->query->where($column, 'LIKE', $value); + } + + return $query; + } + + private function searchIgnoreCase(string $column, string $value): Builder + { + $value = strtolower($value); + return $this->query->whereRaw("LOWER($column) LIKE ?", [$value]); + } +} diff --git a/backend/app/Services/DataTable/Filter/SearchFunctions/FilterEquals.php b/backend/app/Services/DataTable/Filter/SearchFunctions/FilterEquals.php new file mode 100644 index 00000000..594ce87d --- /dev/null +++ b/backend/app/Services/DataTable/Filter/SearchFunctions/FilterEquals.php @@ -0,0 +1,30 @@ +filter->getId(); + $value = $this->filter->getValue(); + + if ($this->filter->getDatatype() == DataType::TEXT->value) { + $query = $this->searchIgnoreCase($column, $value); + } else { + $query = $this->query->where($column, $value); + } + + return $query; + } + + private function searchIgnoreCase(string $column, string $value): Builder + { + $value = strtolower($value); + return $this->query->whereRaw("LOWER($column) = ?", [$value]); + } +} diff --git a/backend/app/Services/DataTable/Filter/SearchFunctions/FilterGreaterThan.php b/backend/app/Services/DataTable/Filter/SearchFunctions/FilterGreaterThan.php new file mode 100644 index 00000000..30da5d32 --- /dev/null +++ b/backend/app/Services/DataTable/Filter/SearchFunctions/FilterGreaterThan.php @@ -0,0 +1,18 @@ +filter->getDatatype() == DataType::NUMERIC) ? + (float)$this->filter->getValue() : $this->filter->getValue(); + + return $this->query->where($this->filter->getId(), '>', $value); + } +} diff --git a/backend/app/Services/DataTable/Filter/SearchFunctions/FilterGreaterThanOrEqual.php b/backend/app/Services/DataTable/Filter/SearchFunctions/FilterGreaterThanOrEqual.php new file mode 100644 index 00000000..8e97248a --- /dev/null +++ b/backend/app/Services/DataTable/Filter/SearchFunctions/FilterGreaterThanOrEqual.php @@ -0,0 +1,18 @@ +filter->getDatatype() == DataType::NUMERIC) ? + (float)$this->filter->getValue() : $this->filter->getValue(); + + return $this->query->where($this->filter->getId(), '>=', $value); + } +} diff --git a/backend/app/Services/DataTable/Filter/SearchFunctions/FilterLessThan.php b/backend/app/Services/DataTable/Filter/SearchFunctions/FilterLessThan.php new file mode 100644 index 00000000..4069b9e0 --- /dev/null +++ b/backend/app/Services/DataTable/Filter/SearchFunctions/FilterLessThan.php @@ -0,0 +1,18 @@ +filter->getDatatype() == DataType::NUMERIC) ? + (float)$this->filter->getValue() : $this->filter->getValue(); + + return $this->query->where($this->filter->getId(), '<', $value); + } +} diff --git a/backend/app/Services/DataTable/Filter/SearchFunctions/FilterLessThanOrEqual.php b/backend/app/Services/DataTable/Filter/SearchFunctions/FilterLessThanOrEqual.php new file mode 100644 index 00000000..84409052 --- /dev/null +++ b/backend/app/Services/DataTable/Filter/SearchFunctions/FilterLessThanOrEqual.php @@ -0,0 +1,18 @@ +filter->getDatatype() == DataType::NUMERIC) ? + (float)$this->filter->getValue() : $this->filter->getValue(); + + return $this->query->where($this->filter->getId(), '<=', $value); + } +} diff --git a/backend/app/Services/DataTable/Filter/SearchFunctions/FilterNotEquals.php b/backend/app/Services/DataTable/Filter/SearchFunctions/FilterNotEquals.php new file mode 100644 index 00000000..e525f2b7 --- /dev/null +++ b/backend/app/Services/DataTable/Filter/SearchFunctions/FilterNotEquals.php @@ -0,0 +1,17 @@ +filter->getId(); + $value = $this->filter->getValue(); + + return $this->query->whereNot($column, $value); + } +} diff --git a/backend/app/Services/DataTable/Filter/SearchFunctions/SearchFilter.php b/backend/app/Services/DataTable/Filter/SearchFunctions/SearchFilter.php new file mode 100644 index 00000000..d4dcbf6a --- /dev/null +++ b/backend/app/Services/DataTable/Filter/SearchFunctions/SearchFilter.php @@ -0,0 +1,22 @@ +query; + + if (!is_null($this->sort)) { + $query->orderBy($this->sort->getId(), $this->sort->getDirection()); + } + + return $query; + } +} diff --git a/backend/app/Services/DataTable/Sort/Sort.php b/backend/app/Services/DataTable/Sort/Sort.php new file mode 100644 index 00000000..5196797a --- /dev/null +++ b/backend/app/Services/DataTable/Sort/Sort.php @@ -0,0 +1,39 @@ +isValid($this, $this->allowedSortings); + } + + /** + * @return string + */ + public function getId(): string + { + return $this->id; + } + + public function getDirection(): string + { + return $this->desc === true ? 'desc' : 'asc'; + } +} diff --git a/backend/app/Services/DataTable/Validators/FilterValidator.php b/backend/app/Services/DataTable/Validators/FilterValidator.php new file mode 100644 index 00000000..ea837f9e --- /dev/null +++ b/backend/app/Services/DataTable/Validators/FilterValidator.php @@ -0,0 +1,69 @@ +isAllowed($filter, $allowedFilters)) { + $filterId = $filter->getId(); + throw new InvalidFilterException($filter->getId(), "filtering field `$filterId` is not allowed."); + } + + if (!$this->isValidSearchFunction($filter)) { + $searchFunction = $filter->getFn(); + throw new InvalidFilterException($searchFunction, "search function `$searchFunction` is invalid."); + } + + if ($this->isValidDataType($filter) == -1) { + throw new InvalidFilterException(null, "datatype property is not set in `filters` array."); + } + + if (!$this->isValidDataType($filter)) { + $datatype = $filter->getDatatype(); + throw new InvalidFilterException($datatype, "datatype `$datatype` is invalid."); + } + + return true; + } + + protected function isAllowed(Filter $filter, array $allowedFilters): bool + { + return $allowedFilters == ['*'] || in_array($filter->getId(), $allowedFilters); + } + + protected function isValidSearchFunction(Filter $filter): bool + { + $searchFunction = $filter->getFn(); + return isset($searchFunction) && in_array($searchFunction, SearchType::values()); + } + + protected function isValidDataType(Filter $filter): int + { + if (!property_exists($filter, 'datatype')) + return -1; + + return $filter->getDatatype() && in_array($filter->getDatatype(), DataType::values()) ? 1 : 0; + } +} diff --git a/backend/app/Services/DataTable/Validators/RelationValidator.php b/backend/app/Services/DataTable/Validators/RelationValidator.php new file mode 100644 index 00000000..ab87382b --- /dev/null +++ b/backend/app/Services/DataTable/Validators/RelationValidator.php @@ -0,0 +1,25 @@ +isAllowed($sorting, $allowedSortings)) { + $sortId = $sorting->getId(); + throw new InvalidSortingException($sortId, "sorting field `$sortId` is not allowed."); + } + + return true; + } + + protected function isAllowed(Sort $sorting, array $allowedSortings): bool + { + return $allowedSortings == ['*'] || in_array($sorting->getId(), $allowedSortings); + } +} diff --git a/backend/bootstrap/providers.php b/backend/bootstrap/providers.php index fc94ae60..069e33bc 100644 --- a/backend/bootstrap/providers.php +++ b/backend/bootstrap/providers.php @@ -1,7 +1,6 @@ Date: Tue, 5 May 2026 17:33:34 +0330 Subject: [PATCH 15/61] write for user --- backend/app/Http/Resources/UserResource.php | 23 ++ backend/app/Models/User.php | 18 +- backend/app/Providers/AppServiceProvider.php | 2 +- backend/app/Traits/ApiResponse.php | 0 .../User/Controller/Auth/AuthController.php | 44 +-- .../app/User/Controller/ProfileController.php | 61 ++++ .../app/User/Requests/Auth/LoginRequest.php | 3 +- .../User/Requests/Auth/RegisterRequest.php | 6 +- .../Profile/ChangePasswordRequest.php | 30 ++ .../app/User/Requests/Profile/EditRequest.php | 37 +++ backend/bootstrap/app.php | 2 +- backend/config/auth.php | 0 backend/config/cors.php | 34 ++ backend/config/session.php | 0 backend/database/factories/UserFactory.php | 2 +- .../0001_01_01_000000_create_users_table.php | 24 +- backend/database/seeders/DatabaseSeeder.php | 3 +- backend/resources/lang/en/auth.php | 19 ++ backend/resources/lang/en/messages.php | 7 + backend/resources/lang/en/pagination.php | 19 ++ backend/resources/lang/en/passwords.php | 22 ++ backend/resources/lang/en/validation.php | 151 +++++++++ backend/resources/lang/fa.json | 137 ++++++++ backend/resources/lang/fa/auth.php | 19 ++ backend/resources/lang/fa/messages.php | 7 + backend/resources/lang/fa/pagination.php | 19 ++ backend/resources/lang/fa/passwords.php | 22 ++ backend/resources/lang/fa/validation.php | 292 ++++++++++++++++++ backend/routes/api.php | 10 - backend/routes/web.php | 21 +- 30 files changed, 969 insertions(+), 65 deletions(-) create mode 100755 backend/app/Http/Resources/UserResource.php mode change 100644 => 100755 backend/app/Models/User.php mode change 100644 => 100755 backend/app/Providers/AppServiceProvider.php mode change 100644 => 100755 backend/app/Traits/ApiResponse.php mode change 100644 => 100755 backend/app/User/Controller/Auth/AuthController.php create mode 100644 backend/app/User/Controller/ProfileController.php mode change 100644 => 100755 backend/app/User/Requests/Auth/LoginRequest.php mode change 100644 => 100755 backend/app/User/Requests/Auth/RegisterRequest.php create mode 100644 backend/app/User/Requests/Profile/ChangePasswordRequest.php create mode 100644 backend/app/User/Requests/Profile/EditRequest.php mode change 100644 => 100755 backend/bootstrap/app.php mode change 100644 => 100755 backend/config/auth.php create mode 100755 backend/config/cors.php mode change 100644 => 100755 backend/config/session.php mode change 100644 => 100755 backend/database/factories/UserFactory.php mode change 100644 => 100755 backend/database/migrations/0001_01_01_000000_create_users_table.php mode change 100644 => 100755 backend/database/seeders/DatabaseSeeder.php create mode 100644 backend/resources/lang/en/auth.php create mode 100644 backend/resources/lang/en/messages.php create mode 100644 backend/resources/lang/en/pagination.php create mode 100644 backend/resources/lang/en/passwords.php create mode 100644 backend/resources/lang/en/validation.php create mode 100644 backend/resources/lang/fa.json create mode 100644 backend/resources/lang/fa/auth.php create mode 100644 backend/resources/lang/fa/messages.php create mode 100644 backend/resources/lang/fa/pagination.php create mode 100644 backend/resources/lang/fa/passwords.php create mode 100644 backend/resources/lang/fa/validation.php mode change 100644 => 100755 backend/routes/api.php mode change 100644 => 100755 backend/routes/web.php diff --git a/backend/app/Http/Resources/UserResource.php b/backend/app/Http/Resources/UserResource.php new file mode 100755 index 00000000..30459058 --- /dev/null +++ b/backend/app/Http/Resources/UserResource.php @@ -0,0 +1,23 @@ + + */ + public function toArray(Request $request): array + { + return [ + 'id' => $this->id, + 'username' => $this->username, + 'email' => $this->email, + ]; + } +} diff --git a/backend/app/Models/User.php b/backend/app/Models/User.php old mode 100644 new mode 100755 index 56fbb341..9f73d3cc --- a/backend/app/Models/User.php +++ b/backend/app/Models/User.php @@ -5,30 +5,18 @@ namespace App\Models; // use Illuminate\Contracts\Auth\MustVerifyEmail; use Database\Factories\UserFactory; use Illuminate\Database\Eloquent\Attributes\Fillable; +use Illuminate\Database\Eloquent\Attributes\Guarded; use Illuminate\Database\Eloquent\Attributes\Hidden; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; use Laravel\Passport\HasApiTokens; -#[Fillable(['name', 'email', 'password'])] -#[Hidden(['password', 'remember_token'])] +#[Guarded(['id'])] +#[Hidden(['password'])] class User extends Authenticatable { /** @use HasFactory */ use HasFactory, Notifiable, HasApiTokens; - - /** - * Get the attributes that should be cast. - * - * @return array - */ - protected function casts(): array - { - return [ - 'email_verified_at' => 'datetime', - 'password' => 'hashed', - ]; - } protected $guarded = ['id']; } diff --git a/backend/app/Providers/AppServiceProvider.php b/backend/app/Providers/AppServiceProvider.php old mode 100644 new mode 100755 index 616a7aea..1039668a --- a/backend/app/Providers/AppServiceProvider.php +++ b/backend/app/Providers/AppServiceProvider.php @@ -22,6 +22,6 @@ class AppServiceProvider extends ServiceProvider public function boot(): void { Passport::enablePasswordGrant(); - Passport::personalAccessTokensExpireIn(CarbonInterval::hour(2)); + Passport::personalAccessTokensExpireIn(CarbonInterval::minutes(1)); } } diff --git a/backend/app/Traits/ApiResponse.php b/backend/app/Traits/ApiResponse.php old mode 100644 new mode 100755 diff --git a/backend/app/User/Controller/Auth/AuthController.php b/backend/app/User/Controller/Auth/AuthController.php old mode 100644 new mode 100755 index d6ca266d..d8b59502 --- a/backend/app/User/Controller/Auth/AuthController.php +++ b/backend/app/User/Controller/Auth/AuthController.php @@ -3,12 +3,14 @@ namespace App\User\Controller\Auth; use App\Http\Controllers\Controller; +use App\Http\Resources\UserResource; use App\Models\User; use App\Traits\ApiResponse; use App\User\Requests\Auth\LoginRequest; use App\User\Requests\Auth\RegisterRequest; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Hash; class AuthController extends Controller @@ -17,42 +19,48 @@ class AuthController extends Controller public function register(RegisterRequest $request): JsonResponse { - $user = User::query()->create([ + User::query()->create([ 'username' => $request->username, 'email' => $request->email, 'password' => Hash::make($request->password), ]); - $token = $user->createToken('Register Token')->accessToken; + $request->session()->regenerate(); - return $this->successResponse([ - 'token' => $token, - ]); + return $this->successResponse(); } public function login(LoginRequest $request): JsonResponse { - $uniq_identifier = $request->input('login'); - $user = User::query() - ->where('email', $uniq_identifier) - ->orWhere('username', $uniq_identifier) - ->first(); + $credentials = ['password' => $request->password,]; - - if (!$user || !Hash::check($request->password, $user->password)) { - return $this->errorResponse(''); + if (filter_var($request->login, FILTER_VALIDATE_EMAIL)) { + $credentials['email'] = $request->login; + } else { + $credentials['username'] = $request->login; } - $token = $user->createToken('Login Token')->accessToken; + if (Auth::attempt($credentials)) + { + $request->session()->regenerate(); - return $this->successResponse([ - 'token' => $token, - ]); + $user = Auth::user(); + + return $this->successResponse([ + 'info' => new UserResource($user), + ]); + } + + return $this->errorResponse(__('messages.incorrect_or_username_password')); } public function logout(Request $request): JsonResponse { - $request->user()->token()->revoke(); + Auth::logout(); + + $request->session()->invalidate(); + + $request->session()->regenerateToken(); return $this->successResponse(); } diff --git a/backend/app/User/Controller/ProfileController.php b/backend/app/User/Controller/ProfileController.php new file mode 100644 index 00000000..b3aa09ce --- /dev/null +++ b/backend/app/User/Controller/ProfileController.php @@ -0,0 +1,61 @@ +successResponse([ + 'info' => new UserResource($user), + ]); + } + + public function edit(EditRequest $request): JsonResponse + { + $user = Auth::user(); + + $user->update([ + 'first_name' => $request->first_name, + 'last_name' => $request->last_name, + 'national_id' => $request->national_id, + 'address' => $request->address, + 'postal_code' => $request->postal_code, + 'account_number' => $request->account_number, + 'phone_number' => $request->phone_number, + 'province_id' => $request->province_id, + 'city_id' => $request->city_id, + ]); + + return $this->successResponse(); + } + + public function changePassword(ChangePasswordRequest $request): JsonResponse + { + $user = Auth::user(); + + if (! Hash::check($request->current_password, $user->password)) { + return $this->errorResponse(__('messages.incorrect_current_password')); + } + + $user->update([ + 'password' => Hash::make($request->new_password), + ]); + + return $this->successResponse(); + + } +} diff --git a/backend/app/User/Requests/Auth/LoginRequest.php b/backend/app/User/Requests/Auth/LoginRequest.php old mode 100644 new mode 100755 index 18442f03..d9b9c881 --- a/backend/app/User/Requests/Auth/LoginRequest.php +++ b/backend/app/User/Requests/Auth/LoginRequest.php @@ -22,8 +22,7 @@ class LoginRequest extends FormRequest public function rules(): array { return [ - 'username' => 'required_without:email|string', - 'email' => 'required_without:name|email', + 'login' => 'required|string', 'password' => 'required|string', ]; } diff --git a/backend/app/User/Requests/Auth/RegisterRequest.php b/backend/app/User/Requests/Auth/RegisterRequest.php old mode 100644 new mode 100755 index 54f8c783..97667b23 --- a/backend/app/User/Requests/Auth/RegisterRequest.php +++ b/backend/app/User/Requests/Auth/RegisterRequest.php @@ -22,9 +22,9 @@ class RegisterRequest extends FormRequest public function rules(): array { return [ - 'username' => 'required_without:email|unique:users,name', - 'email' => 'required_without:name|email|unique:users,email', - 'password' => 'required|string|regex:/^(?=.*[a-zA-Z])(?=.*\d).+$/|min:8', + 'username' => 'required|unique:users,username', + 'email' => 'required|email|unique:users,email', + 'password' => 'required|string|regex:/^(?=.*[a-zA-Z])(?=.*\d).+$/|min:6', ]; } } diff --git a/backend/app/User/Requests/Profile/ChangePasswordRequest.php b/backend/app/User/Requests/Profile/ChangePasswordRequest.php new file mode 100644 index 00000000..60e53c56 --- /dev/null +++ b/backend/app/User/Requests/Profile/ChangePasswordRequest.php @@ -0,0 +1,30 @@ + + */ + public function rules(): array + { + return [ + 'current_password' => 'required|string|regex:/^(?=.*[a-zA-Z])(?=.*\d).+$/|min:6', + 'new_password' => 'required|string|regex:/^(?=.*[a-zA-Z])(?=.*\d).+$/|min:6', + ]; + } +} diff --git a/backend/app/User/Requests/Profile/EditRequest.php b/backend/app/User/Requests/Profile/EditRequest.php new file mode 100644 index 00000000..ddcfcba8 --- /dev/null +++ b/backend/app/User/Requests/Profile/EditRequest.php @@ -0,0 +1,37 @@ + + */ + public function rules(): array + { + return [ + 'first_name' => 'required|string', + 'last_name' => 'required|string', + 'national_id' => 'required', + 'address' => 'required', + 'postal_code' => 'required', + 'account_number' => 'required', + 'phone_number' => 'required|regex:/^964\d{9}$/|numeric|digits:11', + 'province_id' => 'required', + 'city_id' => 'required', + ]; + } +} diff --git a/backend/bootstrap/app.php b/backend/bootstrap/app.php old mode 100644 new mode 100755 index c3928c57..6de85c67 --- a/backend/bootstrap/app.php +++ b/backend/bootstrap/app.php @@ -12,7 +12,7 @@ return Application::configure(basePath: dirname(__DIR__)) health: '/up', ) ->withMiddleware(function (Middleware $middleware): void { - // + $middleware->web()->validateCsrfTokens(['*']); }) ->withExceptions(function (Exceptions $exceptions): void { // diff --git a/backend/config/auth.php b/backend/config/auth.php old mode 100644 new mode 100755 diff --git a/backend/config/cors.php b/backend/config/cors.php new file mode 100755 index 00000000..a76728a2 --- /dev/null +++ b/backend/config/cors.php @@ -0,0 +1,34 @@ + ['*'], + + 'allowed_methods' => ['*'], + + 'allowed_origins' => ['*'], + + 'allowed_origins_patterns' => [], + + 'allowed_headers' => ['*'], + + 'exposed_headers' => [], + + 'max_age' => 0, + + 'supports_credentials' => true, + +]; diff --git a/backend/config/session.php b/backend/config/session.php old mode 100644 new mode 100755 diff --git a/backend/database/factories/UserFactory.php b/backend/database/factories/UserFactory.php old mode 100644 new mode 100755 index c4ceb074..b3301b8b --- a/backend/database/factories/UserFactory.php +++ b/backend/database/factories/UserFactory.php @@ -25,7 +25,7 @@ class UserFactory extends Factory public function definition(): array { return [ - 'name' => fake()->name(), + 'username' => fake()->name(), 'email' => fake()->unique()->safeEmail(), 'email_verified_at' => now(), 'password' => static::$password ??= Hash::make('password'), diff --git a/backend/database/migrations/0001_01_01_000000_create_users_table.php b/backend/database/migrations/0001_01_01_000000_create_users_table.php old mode 100644 new mode 100755 index 05fb5d9e..87b84e12 --- a/backend/database/migrations/0001_01_01_000000_create_users_table.php +++ b/backend/database/migrations/0001_01_01_000000_create_users_table.php @@ -13,18 +13,23 @@ return new class extends Migration { Schema::create('users', function (Blueprint $table) { $table->id(); - $table->string('name'); + $table->string('username'); + $table->string('first_name')->nullable(); + $table->string('last_name')->nullable(); $table->string('email')->unique(); - $table->timestamp('email_verified_at')->nullable(); $table->string('password'); - $table->rememberToken(); - $table->timestamps(); - }); + $table->tinyInteger('status')->default(0); + $table->string('national_id')->nullable(); + $table->string('address')->nullable(); + $table->string('postal_code')->nullable(); + $table->string('account_number')->nullable(); + $table->string('phone_number')->nullable(); + $table->string('province_id')->nullable(); + $table->string('province_name')->nullable(); + $table->string('city_id')->nullable(); + $table->string('city_name')->nullable(); - Schema::create('password_reset_tokens', function (Blueprint $table) { - $table->string('email')->primary(); - $table->string('token'); - $table->timestamp('created_at')->nullable(); + $table->timestamps(); }); Schema::create('sessions', function (Blueprint $table) { @@ -43,7 +48,6 @@ return new class extends Migration public function down(): void { Schema::dropIfExists('users'); - Schema::dropIfExists('password_reset_tokens'); Schema::dropIfExists('sessions'); } }; diff --git a/backend/database/seeders/DatabaseSeeder.php b/backend/database/seeders/DatabaseSeeder.php old mode 100644 new mode 100755 index 6b901f8b..55af5ff8 --- a/backend/database/seeders/DatabaseSeeder.php +++ b/backend/database/seeders/DatabaseSeeder.php @@ -18,8 +18,9 @@ class DatabaseSeeder extends Seeder // User::factory(10)->create(); User::factory()->create([ - 'name' => 'Test User', + 'username' => 'Test User', 'email' => 'test@example.com', + 'password' => 'password123@', ]); } } diff --git a/backend/resources/lang/en/auth.php b/backend/resources/lang/en/auth.php new file mode 100644 index 00000000..e5506df2 --- /dev/null +++ b/backend/resources/lang/en/auth.php @@ -0,0 +1,19 @@ + 'These credentials do not match our records.', + 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.', + +]; diff --git a/backend/resources/lang/en/messages.php b/backend/resources/lang/en/messages.php new file mode 100644 index 00000000..2d88fb58 --- /dev/null +++ b/backend/resources/lang/en/messages.php @@ -0,0 +1,7 @@ + 'The Operation was successful', + 'incorrect_current_password' => 'The current password is incorrect.', + 'incorrect_or_username_password' => 'The username or password is incorrect.', +]; diff --git a/backend/resources/lang/en/pagination.php b/backend/resources/lang/en/pagination.php new file mode 100644 index 00000000..d4814118 --- /dev/null +++ b/backend/resources/lang/en/pagination.php @@ -0,0 +1,19 @@ + '« Previous', + 'next' => 'Next »', + +]; diff --git a/backend/resources/lang/en/passwords.php b/backend/resources/lang/en/passwords.php new file mode 100644 index 00000000..724de4b9 --- /dev/null +++ b/backend/resources/lang/en/passwords.php @@ -0,0 +1,22 @@ + 'Your password has been reset!', + 'sent' => 'We have e-mailed your password reset link!', + 'throttled' => 'Please wait before retrying.', + 'token' => 'This password reset token is invalid.', + 'user' => "We can't find a user with that e-mail address.", + +]; diff --git a/backend/resources/lang/en/validation.php b/backend/resources/lang/en/validation.php new file mode 100644 index 00000000..a65914f9 --- /dev/null +++ b/backend/resources/lang/en/validation.php @@ -0,0 +1,151 @@ + 'The :attribute must be accepted.', + 'active_url' => 'The :attribute is not a valid URL.', + 'after' => 'The :attribute must be a date after :date.', + 'after_or_equal' => 'The :attribute must be a date after or equal to :date.', + 'alpha' => 'The :attribute may only contain letters.', + 'alpha_dash' => 'The :attribute may only contain letters, numbers, dashes and underscores.', + 'alpha_num' => 'The :attribute may only contain letters and numbers.', + 'array' => 'The :attribute must be an array.', + 'before' => 'The :attribute must be a date before :date.', + 'before_or_equal' => 'The :attribute must be a date before or equal to :date.', + 'between' => [ + 'numeric' => 'The :attribute must be between :min and :max.', + 'file' => 'The :attribute must be between :min and :max kilobytes.', + 'string' => 'The :attribute must be between :min and :max characters.', + 'array' => 'The :attribute must have between :min and :max items.', + ], + 'boolean' => 'The :attribute field must be true or false.', + 'confirmed' => 'The :attribute confirmation does not match.', + 'date' => 'The :attribute is not a valid date.', + 'date_equals' => 'The :attribute must be a date equal to :date.', + 'date_format' => 'The :attribute does not match the format :format.', + 'different' => 'The :attribute and :other must be different.', + 'digits' => 'The :attribute must be :digits digits.', + 'digits_between' => 'The :attribute must be between :min and :max digits.', + 'dimensions' => 'The :attribute has invalid image dimensions.', + 'distinct' => 'The :attribute field has a duplicate value.', + 'email' => 'The :attribute must be a valid email address.', + 'ends_with' => 'The :attribute must end with one of the following: :values.', + 'exists' => 'The selected :attribute is invalid.', + 'file' => 'The :attribute must be a file.', + 'filled' => 'The :attribute field must have a value.', + 'gt' => [ + 'numeric' => 'The :attribute must be greater than :value.', + 'file' => 'The :attribute must be greater than :value kilobytes.', + 'string' => 'The :attribute must be greater than :value characters.', + 'array' => 'The :attribute must have more than :value items.', + ], + 'gte' => [ + 'numeric' => 'The :attribute must be greater than or equal :value.', + 'file' => 'The :attribute must be greater than or equal :value kilobytes.', + 'string' => 'The :attribute must be greater than or equal :value characters.', + 'array' => 'The :attribute must have :value items or more.', + ], + 'image' => 'The :attribute must be an image.', + 'in' => 'The selected :attribute is invalid.', + 'in_array' => 'The :attribute field does not exist in :other.', + 'integer' => 'The :attribute must be an integer.', + 'ip' => 'The :attribute must be a valid IP address.', + 'ipv4' => 'The :attribute must be a valid IPv4 address.', + 'ipv6' => 'The :attribute must be a valid IPv6 address.', + 'json' => 'The :attribute must be a valid JSON string.', + 'lt' => [ + 'numeric' => 'The :attribute must be less than :value.', + 'file' => 'The :attribute must be less than :value kilobytes.', + 'string' => 'The :attribute must be less than :value characters.', + 'array' => 'The :attribute must have less than :value items.', + ], + 'lte' => [ + 'numeric' => 'The :attribute must be less than or equal :value.', + 'file' => 'The :attribute must be less than or equal :value kilobytes.', + 'string' => 'The :attribute must be less than or equal :value characters.', + 'array' => 'The :attribute must not have more than :value items.', + ], + 'max' => [ + 'numeric' => 'The :attribute may not be greater than :max.', + 'file' => 'The :attribute may not be greater than :max kilobytes.', + 'string' => 'The :attribute may not be greater than :max characters.', + 'array' => 'The :attribute may not have more than :max items.', + ], + 'mimes' => 'The :attribute must be a file of type: :values.', + 'mimetypes' => 'The :attribute must be a file of type: :values.', + 'min' => [ + 'numeric' => 'The :attribute must be at least :min.', + 'file' => 'The :attribute must be at least :min kilobytes.', + 'string' => 'The :attribute must be at least :min characters.', + 'array' => 'The :attribute must have at least :min items.', + ], + 'not_in' => 'The selected :attribute is invalid.', + 'not_regex' => 'The :attribute format is invalid.', + 'numeric' => 'The :attribute must be a number.', + 'password' => 'The password is incorrect.', + 'present' => 'The :attribute field must be present.', + 'regex' => 'The :attribute format is invalid.', + 'required' => 'The :attribute field is required.', + 'required_if' => 'The :attribute field is required when :other is :value.', + 'required_unless' => 'The :attribute field is required unless :other is in :values.', + 'required_with' => 'The :attribute field is required when :values is present.', + 'required_with_all' => 'The :attribute field is required when :values are present.', + 'required_without' => 'The :attribute field is required when :values is not present.', + 'required_without_all' => 'The :attribute field is required when none of :values are present.', + 'same' => 'The :attribute and :other must match.', + 'size' => [ + 'numeric' => 'The :attribute must be :size.', + 'file' => 'The :attribute must be :size kilobytes.', + 'string' => 'The :attribute must be :size characters.', + 'array' => 'The :attribute must contain :size items.', + ], + 'starts_with' => 'The :attribute must start with one of the following: :values.', + 'string' => 'The :attribute must be a string.', + 'timezone' => 'The :attribute must be a valid zone.', + 'unique' => 'The :attribute has already been taken.', + 'uploaded' => 'The :attribute failed to upload.', + 'url' => 'The :attribute format is invalid.', + 'uuid' => 'The :attribute must be a valid UUID.', + + /* + |-------------------------------------------------------------------------- + | Custom Validation Language Lines + |-------------------------------------------------------------------------- + | + | Here you may specify custom validation messages for attributes using the + | convention "attribute.rule" to name the lines. This makes it quick to + | specify a specific custom language line for a given attribute rule. + | + */ + + 'custom' => [ + 'attribute-name' => [ + 'rule-name' => 'custom-message', + ], + ], + + /* + |-------------------------------------------------------------------------- + | Custom Validation Attributes + |-------------------------------------------------------------------------- + | + | The following language lines are used to swap our attribute placeholder + | with something more reader friendly such as "E-Mail Address" instead + | of "email". This simply helps us make our message more expressive. + | + */ + + 'attributes' => [], + +]; diff --git a/backend/resources/lang/fa.json b/backend/resources/lang/fa.json new file mode 100644 index 00000000..85cf9f1d --- /dev/null +++ b/backend/resources/lang/fa.json @@ -0,0 +1,137 @@ +{ + "Reset Password Notification": "بازیابی رمز عبور", + "You are receiving this email because we received a password reset request for your account.": "شما این ایمیل را برای بازیابی رمز عبور از ما دریافت کرده اید.", + "Reset Password": "بازیابی رمز عبور", + "This password reset link will expire in :count minutes.": "این لینک بازیابی در :count دقیقه دیگر منقضی میشود.", + "If you did not request a password reset, no further action is required.": "اگر شما برای بازیابی رمز عبور درخواست نداده اید این ایمیل را نادیده بگیرید.", + "Verify Email Address": "تایید آدرس ایمیل", + "Please click the button below to verify your email address.": "برای تایید ایمیل لطفا بر ری دکمه زیر کلیک کنید", + "If you did not create an account, no further action is required.": "اگر شما برای تایید حساب درخواست نداده اید این ایمیل را نادیده بگیرید.", + "Login": "ورود", + "E-Mail Address": "ادرس ایمیل", + "Password": "رمز عبور", + "Remember Me": "مرا به خاطر بسپار", + "Forgot Your Password?": "رمز عبورتان را فراموش کرده اید؟", + "Register": "ثبت نام", + "Name": "ثبت نام", + "Confirm Password": "تایید رمز عبور", + "Verify Your Email Address": "ایمیل خود را تایید کنید", + "A fresh verification link has been sent to your email address.": "یک لینک فعال سازی جدید به ادرس ایمیل شما ارسال شد.", + "Before proceeding, please check your email for a verification link.": "قبل از ادامه دادن لطفا ایمیل خود را برای فعال سازی حسابتان برسی کنید.", + "If you did not receive the email": "اگه ایمیلی دریافت نکرده اید", + "click here to request another": "برای درخواست لینک جدید اینجا کلیک کنید", + "Please confirm your password before continuing.": "لطفا برای ادامه رمز عبور خود را تایید کنید", + "Send Password Reset Link": "ارسال لینک بازیابی رمز عبور", + "Logout": "خروج", + "Dashboard": "داشبورد", + "Manage Account": "مدیریت حساب کاربری", + "Profile": "پروفایل", + "API Tokens": "توکن های api", + "Manage Team": "مدیریت تیم", + "Team Settings": "تنظیمات تیم", + "Create New Team": "ساخت تیم جدید", + "Switch Teams": "تعویض تیم", + "Delete Account": "حذف حساب کاربری", + "Permanently delete your account.": ".حذف دائمی حساب کاربری", + "Once your account is deleted, all of its resources and data will be permanently deleted. Before deleting your account, please download any data or information that you wish to retain.": "زمانی که حساب کاربریتان حذف شد، همه اطلاعات به صورت دائم حذف خواهد شد. قبل از حذف همه اطلاعات مورد نیاز خودتان را دانلود کنید.", + "Are you sure you want to delete your account? Once your account is deleted, all of its resources and data will be permanently deleted. Please enter your password to confirm you would like to permanently delete your account.": "آیا مطمئن هستید میخواهید حساب کاربریتان را حذف کنید؟ زمانی که حساب کاربریتان حذف شد، همه اطلاعات به صورت دائم حذف میشود. لطفا رمز عبور خود را برای حذف دائمی حسابتان وارد کنین.", + "Nevermind": "بیخیال", + "Browser Sessions": "دستگاه های فعال", + "Manage and logout your active sessions on other browsers and devices.": "مدیریت و خروج از بقیه دستگاه ها و براورز های فعال.", + "If necessary, you may logout of all of your other browser sessions across all of your devices. If you feel your account has been compromised, you should also update your password.": "اگر نیاز میدونید، میتوانید تمام دستگاه ها و براوزر های فعال این حساب غیر فعال کنید. اگر احساس میکنید حسابتان ممکن است در خطر هست بهتر است رمز عبورتان را هم اپدیت کنید.", + "This device": "این دستگاه", + "Last active": "آخرین زمان فعالیت", + "Done": "انجام شد.", + "Please enter your password to confirm you would like to logout of your other browser sessions across all of your devices.": "برای خروج از بقیه دستگاه ها رمز عبور خود را وارد کنید.", + "Logout Other Browser Sessions": "خروج از بقیه دستگاه ها", + "Two Factor Authentication": "احراز هویت دو فاکتور", + "Add additional security to your account using two factor authentication.": "امنیت بیشتری به حساب خود با استفاده از احراز هویت دو فاکتور اضافه کنید.", + "You have enabled two factor authentication.": "احراز هویت دو فاکتور شما فعال است.", + "You have not enabled two factor authentication.": "شما احراز هویت دو فاکتور را فعال نکرده اید!", + "When two factor authentication is enabled, you will be prompted for a secure, random token during authentication. You may retrieve this token from your phone's Google Authenticator application.": "زمانی که احراز هویت دو فاکتور فعال باشد، در هنگام احراز هویت از شما یک رمز امن و تصادفی درخواست می شود. شما می توانید این رمز را از برنامه Google Authenticator تلفن خود دریافت کنید.", + "Two factor authentication is now enabled. Scan the following QR code using your phone's authenticator application.": "احراز هویت دو فاکتور اکنون فعال شده است. کد QR زیر را با استفاده از برنامه authenticator خود اسکن کنید.", + "Store these recovery codes in a secure password manager. They can be used to recover access to your account if your two factor authentication device is lost.": "این کدهای بازیابی را در یک برنامه مدیریت رمز عبور امن ذخیره کنید. اگر دستگاه تأیید اعتبار دو فاکتور شما از بین رفته باشد ، می توان از آنها برای بازیابی دسترسی به حساب شما استفاده کرد.", + "Enable": "فعال کردن", + "Regenerate Recovery Codes": "تولید دوباره کد های بازیابی", + "Show Recovery Codes": "نمایش کد های بازیابی", + "Disable": "غیر فعال کردن", + "Update Password": "اپدیت رمز عبور", + "Ensure your account is using a long, random password to stay secure.": "اطمینان حاصل کنید که حساب شما از یک رمز عبور تصادفی طولانی برای ایمن ماندن استفاده می کند.", + "Current Password": "رمز عبور فعلی", + "New Password": "رمز عبور جدید", + "Saved.": "ذخیره شد.", + "Save": "ذخیره", + "Profile Information": "اطلاعات پروفایل", + "Update your account's profile information and email address.": "اطلاعات حساب کاربری و ایمیل خود را اپدیت کنید", + "Photo": "عکس", + "Select A New Photo": "انتخاب عکس جدید", + "Remove Photo": "حذف عکس", + "Forgot your password? No problem. Just let us know your email address and we will email you a password reset link that will allow you to choose a new one.": "رمز عبور خود را فراموش کرده اید؟ مشکلی نیست فقط آدرس ایمیل خود را وارد کنید و ما یک ایمیل تنظیم مجدد رمز عبور را برای شما ارسال خواهیم کرد که به شما امکان می دهد رمز عبور جدیدی را انتخاب کنید.", + "Email Password Reset Link": "ایمیل بازیابی رمز عبور", + "Email": "ایمیل", + "Remember me": "مرا به خاطر داشته باش", + "Forgot your password?": "رمز عبور خود را فراموش کرده اید؟", + "Already registered?": "قبلا ثبت نام کرده اید؟", + "Please confirm access to your account by entering the authentication code provided by your authenticator application.": "لطفاً با وارد کردن کد احراز هویت ارائه شده توسط برنامه تایید اعتبار، دسترسی به حساب خود را تأیید کنید.", + "Please confirm access to your account by entering one of your emergency recovery codes.": "لطفاً با وارد کردن یکی از کدهای بازیابی اضطراری، دسترسی به حساب خود را تأیید کنید.", + "Code": "کد", + "Recovery Code": "کد بازیابی", + "Use a recovery code": "استفاده از کد بازیابی", + "Use an authentication code": "استفاده از کد احراز هویت", + "Thanks for signing up! Before getting started, could you verify your email address by clicking on the link we just emailed to you? If you didn't receive the email, we will gladly send you another.": "از ثبت نام شما سپاسگزاریم! قبل از شروع، آیا می توانید آدرس ایمیل خود را با کلیک بر روی لینکی که برای شما فرستادیم، تأیید کنید؟ اگر ایمیل را دریافت نکردید ، ما با کمال میل ایمیل دیگری را برای شما ارسال خواهیم کرد.", + "A new verification link has been sent to the email address you provided during registration.": "لینک تأیید جدید به آدرس ایمیلی که هنگام ثبت نام وارد کرده اید ارسال شده است.", + "Resend Verification Email": "ارسال دوباره تایید ایمیل", + "Create API Token": "ساخت یک توکن", + "API tokens allow third-party services to authenticate with our application on your behalf.": "توکن های API به سرویس های شخص ثالث اجازه می دهد تا از طریق شما با برنامه ما احراز هویت شوند", + "Token Name": "اسم توکن", + "Permissions": "دسترسی ها", + "Created.": "ساخته شد.", + "Create": "ساختن", + "Manage API Tokens": "مدیریت توکن ها", + "You may delete any of your existing tokens if they are no longer needed.": "میتوانید هر یک از توکن های موجود را پاک کنید اگه نیازی به انها ندارید.", + "Last used": "اخرین زمان مورد استفاده", + "Delete": "حذف", + "Please copy your new API token. For your security, it won't be shown again.": "لطفا توکن API خود را کپی کنید. برای امنیت شما ، دوباره نمایش داده نمی شود.", + "Close": "بستن", + "API Token Permissions": "دسترسی های توکن", + "Delete API Token": "حذف توکن", + "Are you sure you would like to delete this API token?": "آیا از حذف این توکن مطمئن هستید؟", + "The :attribute must be a valid role": "فیلد :attribute باید نقش درستی داشته باشد", + "The provided two factor authentication code was invalid.": "رمز دوم شما درست نیست.", + "The provided password does not match your current password.": "رمز عبور وارد شده با رمز عبور حسابتان یکی نیست!", + "The :attribute must be at least :length characters and contain at least one uppercase character.": "فیلد :attribute باید حداقل :length کاراکتر و شامل حداقل یک حرف بزرگ باشد.", + "The :attribute must be at least :length characters and contain at least one number.": "فیلد :attribute باید حداقل :length کاراکتر و شامل حداقل یک عدد باشد.", + "The :attribute must be at least :length characters and contain at least one uppercase character and number.": "فیلد :attribute باید حداقل :length کاراکتر و شامل حداقل یک حرف بزرگ و یک عدد باشد.", + "The :attribute must be at least :length characters.": "فیلد :attribute باید حداقل :length کاراکتر باشد.", + "Team Details": "جزئیات تیم", + "Create a new team to collaborate with others on projects.": "یک تیم جدید برای همکاری با دیگران در پروژه ها ایجاد کنید.", + "Team Owner": "مالک تیم", + "Team Name": "نام تیم", + "Delete Team": "حذف تیم", + "Permanently delete this team.": "حذف دائمی این تیم.", + "Once a team is deleted, all of its resources and data will be permanently deleted. Before deleting this team, please download any data or information regarding this team that you wish to retain.": "پس از حذف یک تیم ، تمام منابع و داده های آن برای همیشه حذف می شوند. قبل از حذف این تیم ، لطفاً هرگونه اطلاعات یا اطلاعات مربوط به این تیم را که می خواهید حفظ کنید دانلود کنید.", + "Are you sure you want to delete this team? Once a team is deleted, all of its resources and data will be permanently deleted.": "آیا مطمئن هستید که می خواهید این تیم را حذف کنید؟ پس از حذف یک تیم ، تمام منابع و داده های آن برای همیشه حذف می شوند.", + "Add Team Member": "افزودن عضو تیم", + "Add a new team member to your team, allowing them to collaborate with you.": "عضوی جدید از تیم را به تیم خود اضافه کنید و به وی اجازه دهید با شما همکاری کند.", + "Please provide the email address of the person you would like to add to this team. The email address must be associated with an existing account.": "لطفاً آدرس ایمیل شخصی را که می خواهید به این تیم اضافه کنید ، وارد کنید. آدرس ایمیل باید با یک حساب موجود مرتبط باشد.", + "Role": "نقش", + "Added.": "اضافه شد", + "Add": "اضافه", + "Team Members": "اعضای تیم", + "All of the people that are part of this team.": "همه افرادی که در این تیم عضو هستند.", + "Leave": "رفتن", + "Remove": "پاک کردن", + "Manage Role": "مدیریت نقش", + "Leave Team": "ترک کردن تیم", + "Are you sure you would like to leave this team?": "آیا مطمئن هستید که می خواهید این تیم را ترک کنید؟", + "Remove Team Member": "حذف عضو تیم", + "Are you sure you would like to remove this person from the team?": "آیا مطمئن هستید که می خواهید این شخص را از تیم حذف کنید؟", + "The team's name and owner information.": "نام تیم و اطلاعات مالک آن.", + "You may not leave a team that you created.": "نمیتوانید از تیم خودتان خارج شوید.", + "You may not delete your personal team.": "نمیتوانید تیم خودتان را حذف کنید.", + "This password does not match our records.": "این رمز عبور با اطلاعات ما مطابق نیست.", + "The :attribute must be a valid role.": "فیلد :attribute باید یک نقش درست باشد.", + "The :attribute must be at least :length characters and contain at least one special character.": "فیلد :attribute باید حداقل :length کاراکتر و شامل یک کاراکتر مخصوص باشد.", + "The :attribute must be at least :length characters and contain at least one uppercase character and one special character.": "فیلد :attribute باید حداقل :length کاراکتر و شامل یک کاراکتر مخصوص و یک حرف بزرگ باشد.", + "The :attribute must be at least :length characters and contain at least one uppercase character, one number, and one special character.": "فیلد :attribute باید حداقل :length کاراکتر، یک کاراکتر مخصوص، یک حرف بزرگ و یک عدد باشد." +} \ No newline at end of file diff --git a/backend/resources/lang/fa/auth.php b/backend/resources/lang/fa/auth.php new file mode 100644 index 00000000..8e208f0a --- /dev/null +++ b/backend/resources/lang/fa/auth.php @@ -0,0 +1,19 @@ + 'اطلاعات وارد شده صحیح نمی باشد.', + 'throttle' => 'تعداد تلاش های ناموفق زیاد بود . لطفا بعد از :seconds ثانیه ی دیگر تلاش کنید .', + +]; \ No newline at end of file diff --git a/backend/resources/lang/fa/messages.php b/backend/resources/lang/fa/messages.php new file mode 100644 index 00000000..ddab3323 --- /dev/null +++ b/backend/resources/lang/fa/messages.php @@ -0,0 +1,7 @@ + 'عملیات موفق آمیز بود', + 'incorrect_current_password' => 'رمز عبور فعلی اشتباه است', + 'incorrect_or_username_password' => 'رمز عبور یا نام کاربری اشتباه است' +]; diff --git a/backend/resources/lang/fa/pagination.php b/backend/resources/lang/fa/pagination.php new file mode 100644 index 00000000..0e75c41f --- /dev/null +++ b/backend/resources/lang/fa/pagination.php @@ -0,0 +1,19 @@ + '« قبلی', + 'next' => 'بعدی »', + +]; \ No newline at end of file diff --git a/backend/resources/lang/fa/passwords.php b/backend/resources/lang/fa/passwords.php new file mode 100644 index 00000000..c3f4fae9 --- /dev/null +++ b/backend/resources/lang/fa/passwords.php @@ -0,0 +1,22 @@ + 'رمز عبور شما با موفقیت تغییر یافت!', + 'sent' => 'لینک تغییر رمز عبور برای شما فرستاده شد!', + 'throttled' => 'لطفا قبل تلاش مجدد منتظر بمانید.', + 'token' => 'token تغییر گذر واژه (لینک) نامعتبر است.', + 'user' => "کاربری با این ایمیل یافت نشد.", + +]; \ No newline at end of file diff --git a/backend/resources/lang/fa/validation.php b/backend/resources/lang/fa/validation.php new file mode 100644 index 00000000..d58a36b0 --- /dev/null +++ b/backend/resources/lang/fa/validation.php @@ -0,0 +1,292 @@ + ':attribute در این حالت مجاز نیست.', + "accepted" => ":attribute باید پذیرفته شده باشد.", + "active_url" => "آدرس :attribute معتبر نیست", + "after" => ":attribute باید بعد از :date باشد.", + 'after_or_equal' => ':attribute باید بعد از یا برابر تاریخ :date باشد.', + "alpha" => ":attribute باید شامل حروف الفبا باشد.", + "alpha_dash" => ":attribute باید شامل حروف الفبا و عدد و خظ تیره(-) باشد.", + "alpha_num" => ":attribute باید شامل حروف الفبا و عدد باشد.", + "array" => ":attribute باید شامل آرایه باشد.", + "before" => ":attribute باید تاریخی قبل از :date باشد.", + 'before_or_equal' => ':attribute باید قبل از یا برابر تاریخ :date باشد.', + "between" => [ + "numeric" => ":attribute باید بین :min و :max باشد.", + "file" => ":attribute باید بین :min و :max کیلوبایت باشد.", + "string" => ":attribute باید بین :min و :max کاراکتر باشد.", + "array" => ":attribute باید بین :min و :max آیتم باشد.", + ], + "boolean" => "فیلد :attribute فقط میتواند صحیح و یا غلط باشد", + "confirmed" => ":attribute با تاییدیه مطابقت ندارد.", + "date" => ":attribute یک تاریخ معتبر نیست.", + 'date_equals' => ':attribute باید برابر تاریخ :date باشد.', + "date_format" => ":attribute با الگوی :format مطاقبت ندارد.", + "different" => ":attribute و :other باید متفاوت باشند.", + "digits" => ":attribute باید :digits رقم باشد.", + "digits_between" => ":attribute باید بین :min و :max رقم باشد.", + 'dimensions' => 'dimensions مربوط به فیلد :attribute اشتباه است.', + 'distinct' => ':attribute مقدار تکراری دارد.', + "email" => "فرمت :attribute معتبر نیست.", + 'ends_with' => ':attribute باید با این مقدار تمام شود: :values.', + "exists" => ":attribute انتخاب شده، معتبر نیست.", + 'file' => 'فیلد :attribute باید فایل باشد.', + "filled" => "فیلد :attribute الزامی است", + 'gt' => [ + 'numeric' => ':attribute باید بیشتر از :value باشد.', + 'file' => ':attribute باید بیشتر از :value کیلوبایت باشد.', + 'string' => ':attribute باید بیشتر از :value کاراکتر باشد.', + 'array' => ':attribute باید بیشتر از :value ایتم باشد.', + ], + 'gte' => [ + 'numeric' => ':attribute باید بیشتر یا برابر :value باشد.', + 'file' => ':attribute باید بیشتر یا برابر :value کیلوبایت باشد.', + 'string' => ':attribute باید بیشتر یا برابر :value کاراکتر باشد.', + 'array' => ':attribute باید :value ایتم یا بیشتر را داشته باشد.', + ], + "image" => ":attribute باید تصویر باشد.", + "in" => ":attribute انتخاب شده، معتبر نیست.", + "integer" => ":attribute باید نوع داده ای عددی (integer) باشد.", + "ip" => ":attribute باید IP آدرس معتبر باشد.", + 'ipv4' => ':attribute باید یک ادرس درست IPv4 باشد.', + 'ipv6' => ':attribute باید یک ادرس درست IPv6 باشد.', + 'json' => ':attribute یک مقدار درست JSON باشد.', + 'lt' => [ + 'numeric' => ':attribute باید کمتر از :value باشد.', + 'file' => ':attribute باید کمتر از :value کیلوبایت باشد.', + 'string' => ':attribute باید کمتر از :value کاراکتر باشد.', + 'array' => ':attribute باید :value ایتم یا کمتر را داشته باشد.', + ], + 'lte' => [ + 'numeric' => ':attribute باید کمتر یا برابر :value باشد.', + 'file' => ':attribute باید کمتر یا برابر :value کیلوبایت باشد.', + 'string' => ':attribute باید کمتر یا برابر :value کاراکتر باشد.', + 'array' => ':attribute باید :value ایتم یا کمتر را داشته باشد.', + ], + "max" => [ + "numeric" => ":attribute نباید بزرگتر از :max باشد.", + "file" => ":attribute نباید بزرگتر از :max کیلوبایت باشد.", + "string" => ":attribute نباید بیشتر از :max کاراکتر باشد.", + "array" => ":attribute نباید بیشتر از :max آیتم باشد.", + ], + "mimes" => ":attribute باید یکی از فرمت های :values باشد.", + 'mimetypes' => ':attribute باید تایپ ان از نوع: :values باشد.', + "min" => [ + "numeric" => ":attribute نباید کوچکتر از :min باشد.", + "file" => ":attribute نباید کوچکتر از :min کیلوبایت باشد.", + "string" => ":attribute نباید کمتر از :min کاراکتر باشد.", + "array" => ":attribute نباید کمتر از :min آیتم باشد.", + ], + "not_in" => ":attribute انتخاب شده، معتبر نیست.", + 'not_regex' => ':attribute فرمت معتبر نیست.', + "numeric" => ":attribute باید شامل عدد باشد.", + 'password' => 'رمز عبور اشتباه است.', + 'present' => ':attribute باید وجود داشته باشد.', + "regex" => ":attribute یک فرمت معتبر نیست", + "required" => "فیلد :attribute الزامی است", + "required_if" => "فیلد :attribute هنگامی که :other برابر با :value است، الزامیست.", + 'required_unless' => 'قیلد :attribute الزامیست مگر این فیلد :other مقدارش :values باشد.', + "required_with" => ":attribute الزامی است زمانی که :values موجود است.", + "required_with_all" => ":attribute الزامی است زمانی که :values موجود است.", + "required_without" => ":attribute الزامی است زمانی که :values موجود نیست.", + "required_without_all" => ":attribute الزامی است زمانی که :values موجود نیست.", + "same" => ":attribute و :other باید مانند هم باشند.", + "size" => [ + "numeric" => ":attribute باید برابر با :size باشد.", + "file" => ":attribute باید برابر با :size کیلوبایت باشد.", + "string" => ":attribute باید برابر با :size کاراکتر باشد.", + "array" => ":attribute باسد شامل :size آیتم باشد.", + ], + 'starts_with' => ':attribute باید با یکی از این مقادیر شروع شود: :values.', + "string" => ":attribute باید رشته باشد.", + "timezone" => "فیلد :attribute باید یک منطقه صحیح باشد.", + "unique" => ":attribute قبلا انتخاب شده است.", + 'uploaded' => 'فیلد :attribute به درستی اپلود نشد.', + "url" => "فرمت آدرس :attribute اشتباه است.", + 'uuid' => ':attribute باید یک فرمت درست UUID باشد.', + + /* + |-------------------------------------------------------------------------- + | Custom Validation Language Lines + |-------------------------------------------------------------------------- + | + | Here you may specify custom validation messages for attributes using the + | convention "attribute.rule" to name the lines. This makes it quick to + | specify a specific custom language line for a given attribute rule. + | + */ + + 'custom' => [], + + /* + |-------------------------------------------------------------------------- + | Custom Validation Attributes + |-------------------------------------------------------------------------- + | + | The following language lines are used to swap attribute place-holders + | with something more reader friendly such as E-Mail Address instead + | of "email". This simply helps us make messages a little cleaner. + | + */ + 'attributes' => [ + "name" => "نام", + "username" => "نام کاربری", + "national_code" => "کد ملی", + "email" => "پست الکترونیکی", + "first_name" => "نام", + "last_name" => "نام خانوادگی", + "family" => "نام خانوادگی", + "password" => "رمز عبور", + "password_confirmation" => "تاییدیه ی رمز عبور", + "city" => "شهر", + "country" => "کشور", + "address" => "نشانی", + "phone" => "تلفن", + "mobile" => "تلفن همراه", + "age" => "سن", + "sex" => "جنسیت", + "gender" => "جنسیت", + "day" => "روز", + "month" => "ماه", + "year" => "سال", + "hour" => "ساعت", + "minute" => "دقیقه", + "second" => "ثانیه", + "title" => "عنوان", + "text" => "متن", + "content" => "محتوا", + "description" => "توضیحات", + "excerpt" => "گلچین کردن", + "date" => "تاریخ", + "time" => "زمان", + "available" => "موجود", + "size" => "اندازه", + "file" => "فایل", + "fullname" => "نام کامل", + 'current_password' => 'رمز عبور فعلی', + 'new_password' => 'رمز عبور جدید', + 'degree' => 'مدرک تحصیلی', + 'major' => 'شغل', + 'avatar' => 'آواتار', + 'lat' => 'طول جغرافیایی', + 'lng' => 'عرض جغرافیایی', + 'azmayesh_type_id' => 'نوع آزمایش', + 'request_date' => 'تاریخ درخواست', + 'report_date' => 'تاریخ گزارش', + 'request_number' => 'شماره درخواست', + 'employer' => 'کارفرما', + 'contractor' => 'پیمانکار', + 'work_number' => 'شماره کار', + 'project_name' => 'نام پروژه', + 'consultant' => 'مشاور', + 'applicant' => 'متقاضی', + 'province_id' => 'استان', + 'azmayesh_id' => 'آزمایش', + 'data' => 'داده', + 'fields' => 'فیلد ها', + 'fields.*.name' => 'نام فیلد', + 'fields.*.unit' => 'واحد فیلد', + 'fields.*.type' => 'نوع فیلد', + 'fields.*.option' => 'گزینه های فیلد', + 'cmms_machine_id' => 'کد یکتا ماشین', + 'contract_subitem_id' => 'کد یکتای پروژه', + 'base_price' => 'قیمت پایه', + 'unit' => 'واحد', + 'phone_number'=> 'شماره تلفن', + 'final_description' => 'توضیحات نهایی', + 'point' => 'منطقه' , + 'info_id' => 'دسته بندی' , + 'activity_date' => 'تاریخ فعالیت', + 'activity_time' => 'زمان فعالیت', + 'recognize_picture' => 'عکس بازدید', + 'recognize_picture_second' => 'عکس بازدید', + 'axis_type_id' => 'نوع محور', + 'supervisor_description' => 'توضیحات کارشناس', + 'need_judiciary' => 'دستور قضایی' , + 'operator_description' => 'توضیحات ناظر', + 'judiciary_document' => 'فایل دستور قضایی', + 'action_picture' => 'عکس اقدامات', + 'finish_picture' => 'عکس پایان کار', + 'action_date' => 'زمان فالیت', + 'evidence_picture' => 'تصویر مشاهده شده', + 'deposit_insurance_image' => 'عکس مبلغ بیمه', + 'deposit_insurance_amount' => ' مبلغ بیمه', + 'deposit_daghi_image' => 'عکس مبلغ بیمه', + 'deposit_daghi_amount' => 'مبلغ داغی', + 'is_foreign' => 'ناوگان خارجی', + 'axis_name' => 'نام محور', + 'driver_name' => 'اسم راننده', + 'plaque' => 'پلاک', + 'driver_national_code' => 'کدملی راننده', + 'driver_phone_number' => 'شماره موبایل راننده', + 'accident_type' => 'نوع خسارت', + 'accident_date' => 'تاریخ تصادف', + 'accident_time' => 'زمان تصادف', + 'report_base' => 'گزارش', + 'police_file' => 'تصویر کروکی یا نامه پلیس راه', + 'police_serial' => 'شماره کروکی یا نامه پلیس راه', + 'police_file_date' => 'تاریخ کروکی یا نامه پلیس راه', + 'damage_picture1' => 'عکس تصادف', + 'damage_picture2' => 'عکس تصادف', + 'damage_items' => 'ایتم های خسارت', + 'damage_items.*.value' => 'میزان ایتم های خسارت', + 'damage_items.*.amount' => 'هزینه خسارت', + 'driver_rate' => 'سهم راننده', + 'code' => 'کد', + 'rahdaran' => 'افراد', + 'machines' => 'خودرو', + 'driver' => 'راننده', + 'zone' => 'منطقه', + 'start_date' => 'تاریخ شروع', + 'end_date' => 'تاریخ پایان', + 'end_point' => 'مقصد', + 'area' => 'منطقه عملیاتی', + 'area.type' => 'نوع محدوده' , + 'area.coordinates' => 'مختصات محدوده', + 'category_id' => 'دسته بندی', + 'explanation' => 'توضیحات', + 'requested_machines' => 'ماشین های درخواستی', + 'type' => 'نوع', + 'road_observed_id' => 'کد واکنش سریع', + 'city_id' => 'شهر', + 'expert_description' => 'توضیح کارشناس', + 'need_payment' => 'نیاز به پرداخت', + 'request_id' => 'شناسه درخواست', + 'access_road' => 'راه دسترسی', + 'access_road.type' => 'نوع راه دسترسی', + 'access_road.coordinates' => 'مختصات راه دسترسی', + 'final_area' => 'محدوده نهایی', + 'final_area.type' => 'نوع محدوده نهایی', + 'final_area.coordinates' => 'مختصات محدوده نهایی', + 'final_plan' => 'طرح نهایی', + 'final_plan.type' => 'نوع طرح نهایی', + 'final_plan.coordinate' => 'مختصات طرح نهایی', + 'national_id' => 'کد ملی' , + 'requested_organization' => 'سازمان درخواست‌کننده', + 'plan_group' => 'گروه طرح', + 'plan_title' => 'موضوع طرح', + 'worksheet' => 'شناسه کاربرگ', + 'response_options' => 'گزینه های پاسخ', + 'isic' => 'کد ISIC', + 'primary_area' => 'محدوده اولیه', + 'primary_area.type' => 'نوع محدوده اولیه', + 'primary_area.coordinates' => 'مختصات محدوده اولیه', + 'forbidden_area' => 'محدوده ممنوعه', + 'forbidden_area.type' => 'نوع محدوده محدوده', + 'forbidden_area.coordinates' => 'مختصات محدوده ممنوعه', + 'edarate_ostani_id' => 'اداره استانی', + 'edarate_shahri_id' => ' اداره شهری' + ], +]; diff --git a/backend/routes/api.php b/backend/routes/api.php old mode 100644 new mode 100755 index fa658a85..56897756 --- a/backend/routes/api.php +++ b/backend/routes/api.php @@ -8,13 +8,3 @@ Route::get('/user', function (Request $request) { return $request->user(); })->middleware('auth:api'); -Route::prefix('user')->group(function () { - Route::prefix('Auth') - ->name('Auth') - ->controller(AuthController::class) - ->group(function () { - Route::post('register', 'register')->name('register'); - Route::post('login', 'login')->name('login'); - Route::post('logout', 'logout')->middleware('auth:api')->name('logout'); - }); -}); diff --git a/backend/routes/web.php b/backend/routes/web.php old mode 100644 new mode 100755 index 86a06c53..ba0244c3 --- a/backend/routes/web.php +++ b/backend/routes/web.php @@ -1,7 +1,22 @@ name('Auth.') + ->controller(AuthController::class) + ->group(function () { + Route::post('register', 'register')->name('register'); + Route::post('login', 'login')->name('login'); + Route::post('logout', 'logout')->name('logout'); + }); +Route::prefix('profile') + ->name('profile.') + ->controller(ProfileController::class) + ->group(function () { + Route::get('info', 'info')->name('info'); + Route::post('edit', 'edit')->name('edit'); + Route::post('change_password', 'changePassword')->name('changePassword'); + }); -- 2.49.1 From 05c54fb2d7bfde4b4b1de5deb6e7560f6f062246 Mon Sep 17 00:00:00 2001 From: amirghasempoor Date: Wed, 6 May 2026 08:49:57 +0330 Subject: [PATCH 16/61] add languages files --- backend/.env.example | 12 ++ backend/app/Facades/File/File.php | 113 +++++++++++ backend/app/Facades/File/FileFacade.php | 13 ++ backend/app/Providers/FileServiceProvider.php | 27 +++ .../Services/FIB/AuthorizationService.php | 6 +- backend/bootstrap/providers.php | 1 + backend/resources/lang/ar/notifications.php | 45 ++++ backend/resources/lang/en/auth.php | 20 ++ backend/resources/lang/en/messages.php | 11 + backend/resources/lang/en/pagination.php | 19 ++ backend/resources/lang/en/passwords.php | 22 ++ backend/resources/lang/en/validation.php | 186 +++++++++++++++++ backend/resources/lang/fa/auth.php | 20 ++ backend/resources/lang/fa/messages.php | 11 + backend/resources/lang/fa/pagination.php | 19 ++ backend/resources/lang/fa/passwords.php | 22 ++ backend/resources/lang/fa/validation.php | 192 ++++++++++++++++++ 17 files changed, 734 insertions(+), 5 deletions(-) create mode 100644 backend/app/Facades/File/File.php create mode 100644 backend/app/Facades/File/FileFacade.php create mode 100644 backend/app/Providers/FileServiceProvider.php create mode 100644 backend/resources/lang/ar/notifications.php create mode 100644 backend/resources/lang/en/auth.php create mode 100644 backend/resources/lang/en/messages.php create mode 100644 backend/resources/lang/en/pagination.php create mode 100644 backend/resources/lang/en/passwords.php create mode 100644 backend/resources/lang/en/validation.php create mode 100644 backend/resources/lang/fa/auth.php create mode 100644 backend/resources/lang/fa/messages.php create mode 100644 backend/resources/lang/fa/pagination.php create mode 100644 backend/resources/lang/fa/passwords.php create mode 100644 backend/resources/lang/fa/validation.php diff --git a/backend/.env.example b/backend/.env.example index 409384df..cff0c7b8 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -63,3 +63,15 @@ AWS_BUCKET= AWS_USE_PATH_STYLE_ENDPOINT=false VITE_APP_NAME="${APP_NAME}" + +FIB_AUTHORIZATION_URL= +FIB_CLIENT_ID= +FIB_CLIENT_SECRET= + +FIB_CREATE_PAYMENT_URL + +FIB_CHECK_PAYMENT_STATUS_URL + +FIB_CANCEL_PAYMENT_URL + +FIB_REFUND_URL diff --git a/backend/app/Facades/File/File.php b/backend/app/Facades/File/File.php new file mode 100644 index 00000000..5cc59955 --- /dev/null +++ b/backend/app/Facades/File/File.php @@ -0,0 +1,113 @@ +putFileAs( + $path, + $file, + $name ?? Str::uuid() . '.' . $file->extension() + ); + } + + /** + * @param string $filePath + * @param bool $url_is_absolute + * @param string $disk + * @return void + * @throws InvalidArgumentException when filePath is empty. + */ + public function delete(string $filePath, bool $url_is_absolute = false, string $disk = 'public'): void + { + if (!$filePath){ + throw new InvalidArgumentException('File path is invalid.'); + } + + if ($url_is_absolute) { + $needle = 'storage/'; + $pos = strpos($filePath, $needle); + $filePath = substr($filePath, $pos + strlen($needle)); + } + + if (Storage::disk($disk)->exists($filePath)) { + Storage::disk($disk)->delete($filePath); + } + } + + public function deleteContent($file_path): bool + { + if (file_exists($file_path)) { + file_put_contents($file_path, ''); + + return true; + } + return false; + } + + public function tail($file_path, $lines = 100, $buffer = 2048): bool|string + { + // Open file + $file = @fopen($file_path, "rb"); + if ($file === false) { + return false; + } + + // Jump to last character + fseek($file, -1, SEEK_END); + + // Read it and adjust line number if necessary + // (Otherwise the result would be wrong if file doesn't end with a blank line) + if (fread($file, 1) != "\n") { + $lines -= 1; + } + + // Start reading + $output = ''; + $chunk = ''; + + // While we would like more + while (ftell($file) > 0 && $lines >= 0) { + + // Figure out how far back we should jump + $seek = min(ftell($file), $buffer); + + // Do the jump (backwards, relative to where we are) + fseek($file, -$seek, SEEK_CUR); + + // Read a chunk and prepend it to our output + $output = ($chunk = fread($file, $seek)) . $output; + + // Jump back to where we started reading + fseek($file, -mb_strlen($chunk, '8bit'), SEEK_CUR); + + // Decrease our line counter + $lines -= substr_count($chunk, "\n"); + } + + // While we have too many lines + // (Because of buffer size we might have read too many) + while ($lines++ < 0) { + + // Find first newline and remove all text before that + $output = substr($output, strpos($output, "\n") + 1); + } + + // Close file and return + fclose($file); + return trim($output); + } + + public function deleteDirectory(string $path, string $disk = 'public'): void + { + Storage::disk($disk)->deleteDirectory($path); + } +} diff --git a/backend/app/Facades/File/FileFacade.php b/backend/app/Facades/File/FileFacade.php new file mode 100644 index 00000000..daa9cd9f --- /dev/null +++ b/backend/app/Facades/File/FileFacade.php @@ -0,0 +1,13 @@ +app->bind('file', function () { + return new File(); + }); + } + + /** + * Bootstrap services. + */ + public function boot(): void + { + // + } +} diff --git a/backend/app/User/Services/FIB/AuthorizationService.php b/backend/app/User/Services/FIB/AuthorizationService.php index 6f6a9422..9e67d55d 100644 --- a/backend/app/User/Services/FIB/AuthorizationService.php +++ b/backend/app/User/Services/FIB/AuthorizationService.php @@ -17,10 +17,6 @@ class AuthorizationService { $this->url = config('fib.authorization.url'); $this->channelName = ''; - } - - public function setInputParameters(): void - { $this->inputParameters = [ 'client_id' => config('fib.authorization.client_id'), 'client_secret' => config('fib.authorization.client_secret'), @@ -53,7 +49,7 @@ class AuthorizationService */ public function sendRequest(): array { - return Http::withHeaders(['Content-Type' => 'application/json']) + return Http::asForm() ->throw() ->withoutVerifying() ->post($this->url, $this->inputParameters) diff --git a/backend/bootstrap/providers.php b/backend/bootstrap/providers.php index 069e33bc..9a7a919f 100644 --- a/backend/bootstrap/providers.php +++ b/backend/bootstrap/providers.php @@ -3,4 +3,5 @@ return [ App\Providers\AppServiceProvider::class, App\Providers\DataTableServiceProvider::class, + App\Providers\FileServiceProvider::class, ]; diff --git a/backend/resources/lang/ar/notifications.php b/backend/resources/lang/ar/notifications.php new file mode 100644 index 00000000..48bc7098 --- /dev/null +++ b/backend/resources/lang/ar/notifications.php @@ -0,0 +1,45 @@ + 'رسالة استثناء: :message', + 'exception_trace' => 'تتبع الإستثناء: :trace', + 'exception_message_title' => 'رسالة استثناء', + 'exception_trace_title' => 'تتبع الإستثناء', + + 'backup_failed_subject' => 'أخفق النسخ الاحتياطي لل :application_name', + 'backup_failed_body' => 'مهم: حدث خطأ أثناء النسخ الاحتياطي :application_name', + + 'backup_successful_subject' => 'نسخ احتياطي جديد ناجح ل :application_name', + 'backup_successful_subject_title' => 'نجاح النسخ الاحتياطي الجديد!', + 'backup_successful_body' => 'أخبار عظيمة، نسخة احتياطية جديدة ل :application_name تم إنشاؤها بنجاح على القرص المسمى :disk_name.', + + 'cleanup_failed_subject' => 'فشل تنظيف النسخ الاحتياطي للتطبيق :application_name .', + 'cleanup_failed_body' => 'حدث خطأ أثناء تنظيف النسخ الاحتياطية ل :application_name', + + 'cleanup_successful_subject' => 'تنظيف النسخ الاحتياطية ل :application_name تمت بنجاح', + 'cleanup_successful_subject_title' => 'تنظيف النسخ الاحتياطية تم بنجاح!', + 'cleanup_successful_body' => 'تنظيف النسخ الاحتياطية ل :application_name على القرص المسمى :disk_name تم بنجاح.', + + 'healthy_backup_found_subject' => 'النسخ الاحتياطية ل :application_name على القرص :disk_name صحية', + 'healthy_backup_found_subject_title' => 'النسخ الاحتياطية ل :application_name صحية', + 'healthy_backup_found_body' => 'تعتبر النسخ الاحتياطية ل :application_name صحية. عمل جيد!', + + 'unhealthy_backup_found_subject' => 'مهم: النسخ الاحتياطية ل :application_name غير صحية', + 'unhealthy_backup_found_subject_title' => 'مهم: النسخ الاحتياطية ل :application_name غير صحية. :problem', + 'unhealthy_backup_found_body' => 'النسخ الاحتياطية ل :application_name على القرص :disk_name غير صحية.', + 'unhealthy_backup_found_not_reachable' => 'لا يمكن الوصول إلى وجهة النسخ الاحتياطي. :error', + 'unhealthy_backup_found_empty' => 'لا توجد نسخ احتياطية لهذا التطبيق على الإطلاق.', + 'unhealthy_backup_found_old' => 'تم إنشاء أحدث النسخ الاحتياطية في :date وتعتبر قديمة جدا.', + 'unhealthy_backup_found_unknown' => 'عذرا، لا يمكن تحديد سبب دقيق.', + 'unhealthy_backup_found_full' => 'النسخ الاحتياطية تستخدم الكثير من التخزين. الاستخدام الحالي هو :disk_usage وهو أعلى من الحد المسموح به من :disk_limit.', + + 'no_backups_info' => 'لم يتم عمل نسخ احتياطية حتى الآن', + 'application_name' => 'اسم التطبيق', + 'backup_name' => 'اسم النسخ الاحتياطي', + 'disk' => 'القرص', + 'newest_backup_size' => 'أحدث حجم للنسخ الاحتياطي', + 'number_of_backups' => 'عدد النسخ الاحتياطية', + 'total_storage_used' => 'إجمالي مساحة التخزين المستخدمة', + 'newest_backup_date' => 'أحدث تاريخ النسخ الاحتياطي', + 'oldest_backup_date' => 'أقدم تاريخ نسخ احتياطي', +]; diff --git a/backend/resources/lang/en/auth.php b/backend/resources/lang/en/auth.php new file mode 100644 index 00000000..6598e2c0 --- /dev/null +++ b/backend/resources/lang/en/auth.php @@ -0,0 +1,20 @@ + 'These credentials do not match our records.', + 'password' => 'The provided password is incorrect.', + 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.', + +]; diff --git a/backend/resources/lang/en/messages.php b/backend/resources/lang/en/messages.php new file mode 100644 index 00000000..b0f55d0f --- /dev/null +++ b/backend/resources/lang/en/messages.php @@ -0,0 +1,11 @@ + 'The operation was successful.', + 'invalid_credential' => 'Provided credential is invalid.', + 'incorrect_current_password' => 'Current password is incorrect.', + 'new_password_email_sent' => 'Your new password sent to your email', + 'file_upload_failed' => 'The file can not be uploaded.', + 'otp' => 'your otp token is : ', + 'incorrect_otp' => 'Incorrect OTP code.', +]; diff --git a/backend/resources/lang/en/pagination.php b/backend/resources/lang/en/pagination.php new file mode 100644 index 00000000..d4814118 --- /dev/null +++ b/backend/resources/lang/en/pagination.php @@ -0,0 +1,19 @@ + '« Previous', + 'next' => 'Next »', + +]; diff --git a/backend/resources/lang/en/passwords.php b/backend/resources/lang/en/passwords.php new file mode 100644 index 00000000..f1223bd7 --- /dev/null +++ b/backend/resources/lang/en/passwords.php @@ -0,0 +1,22 @@ + 'Your password has been reset.', + 'sent' => 'We have emailed your password reset link.', + 'throttled' => 'Please wait before retrying.', + 'token' => 'This password reset token is invalid.', + 'user' => "We can't find a user with that email address.", + +]; diff --git a/backend/resources/lang/en/validation.php b/backend/resources/lang/en/validation.php new file mode 100644 index 00000000..f7bd0407 --- /dev/null +++ b/backend/resources/lang/en/validation.php @@ -0,0 +1,186 @@ + 'The :attribute field must be accepted.', + 'accepted_if' => 'The :attribute field must be accepted when :other is :value.', + 'active_url' => 'The :attribute field must be a valid URL.', + 'after' => 'The :attribute field must be a date after :date.', + 'after_or_equal' => 'The :attribute field must be a date after or equal to :date.', + 'alpha' => 'The :attribute field must only contain letters.', + 'alpha_dash' => 'The :attribute field must only contain letters, numbers, dashes, and underscores.', + 'alpha_num' => 'The :attribute field must only contain letters and numbers.', + 'array' => 'The :attribute field must be an array.', + 'ascii' => 'The :attribute field must only contain single-byte alphanumeric characters and symbols.', + 'before' => 'The :attribute field must be a date before :date.', + 'before_or_equal' => 'The :attribute field must be a date before or equal to :date.', + 'between' => [ + 'array' => 'The :attribute field must have between :min and :max items.', + 'file' => 'The :attribute field must be between :min and :max kilobytes.', + 'numeric' => 'The :attribute field must be between :min and :max.', + 'string' => 'The :attribute field must be between :min and :max characters.', + ], + 'boolean' => 'The :attribute field must be true or false.', + 'confirmed' => 'The :attribute field confirmation does not match.', + 'current_password' => 'The password is incorrect.', + 'date' => 'The :attribute field must be a valid date.', + 'date_equals' => 'The :attribute field must be a date equal to :date.', + 'date_format' => 'The :attribute field must match the format :format.', + 'decimal' => 'The :attribute field must have :decimal decimal places.', + 'declined' => 'The :attribute field must be declined.', + 'declined_if' => 'The :attribute field must be declined when :other is :value.', + 'different' => 'The :attribute field and :other must be different.', + 'digits' => 'The :attribute field must be :digits digits.', + 'digits_between' => 'The :attribute field must be between :min and :max digits.', + 'dimensions' => 'The :attribute field has invalid image dimensions.', + 'distinct' => 'The :attribute field has a duplicate value.', + 'doesnt_end_with' => 'The :attribute field must not end with one of the following: :values.', + 'doesnt_start_with' => 'The :attribute field must not start with one of the following: :values.', + 'email' => 'The :attribute field must be a valid email address.', + 'ends_with' => 'The :attribute field must end with one of the following: :values.', + 'enum' => 'The selected :attribute is invalid.', + 'exists' => 'The selected :attribute is invalid.', + 'file' => 'The :attribute field must be a file.', + 'filled' => 'The :attribute field must have a value.', + 'gt' => [ + 'array' => 'The :attribute field must have more than :value items.', + 'file' => 'The :attribute field must be greater than :value kilobytes.', + 'numeric' => 'The :attribute field must be greater than :value.', + 'string' => 'The :attribute field must be greater than :value characters.', + ], + 'gte' => [ + 'array' => 'The :attribute field must have :value items or more.', + 'file' => 'The :attribute field must be greater than or equal to :value kilobytes.', + 'numeric' => 'The :attribute field must be greater than or equal to :value.', + 'string' => 'The :attribute field must be greater than or equal to :value characters.', + ], + 'image' => 'The :attribute field must be an image.', + 'in' => 'The selected :attribute is invalid.', + 'in_array' => 'The :attribute field must exist in :other.', + 'integer' => 'The :attribute field must be an integer.', + 'ip' => 'The :attribute field must be a valid IP address.', + 'ipv4' => 'The :attribute field must be a valid IPv4 address.', + 'ipv6' => 'The :attribute field must be a valid IPv6 address.', + 'json' => 'The :attribute field must be a valid JSON string.', + 'lowercase' => 'The :attribute field must be lowercase.', + 'lt' => [ + 'array' => 'The :attribute field must have less than :value items.', + 'file' => 'The :attribute field must be less than :value kilobytes.', + 'numeric' => 'The :attribute field must be less than :value.', + 'string' => 'The :attribute field must be less than :value characters.', + ], + 'lte' => [ + 'array' => 'The :attribute field must not have more than :value items.', + 'file' => 'The :attribute field must be less than or equal to :value kilobytes.', + 'numeric' => 'The :attribute field must be less than or equal to :value.', + 'string' => 'The :attribute field must be less than or equal to :value characters.', + ], + 'mac_address' => 'The :attribute field must be a valid MAC address.', + 'max' => [ + 'array' => 'The :attribute field must not have more than :max items.', + 'file' => 'The :attribute field must not be greater than :max kilobytes.', + 'numeric' => 'The :attribute field must not be greater than :max.', + 'string' => 'The :attribute field must not be greater than :max characters.', + ], + 'max_digits' => 'The :attribute field must not have more than :max digits.', + 'mimes' => 'The :attribute field must be a file of type: :values.', + 'mimetypes' => 'The :attribute field must be a file of type: :values.', + 'min' => [ + 'array' => 'The :attribute field must have at least :min items.', + 'file' => 'The :attribute field must be at least :min kilobytes.', + 'numeric' => 'The :attribute field must be at least :min.', + 'string' => 'The :attribute field must be at least :min characters.', + ], + 'min_digits' => 'The :attribute field must have at least :min digits.', + 'missing' => 'The :attribute field must be missing.', + 'missing_if' => 'The :attribute field must be missing when :other is :value.', + 'missing_unless' => 'The :attribute field must be missing unless :other is :value.', + 'missing_with' => 'The :attribute field must be missing when :values is present.', + 'missing_with_all' => 'The :attribute field must be missing when :values are present.', + 'multiple_of' => 'The :attribute field must be a multiple of :value.', + 'not_in' => 'The selected :attribute is invalid.', + 'not_regex' => 'The :attribute field format is invalid.', + 'numeric' => 'The :attribute field must be a number.', + 'password' => [ + 'letters' => 'The :attribute field must contain at least one letter.', + 'mixed' => 'The :attribute field must contain at least one uppercase and one lowercase letter.', + 'numbers' => 'The :attribute field must contain at least one number.', + 'symbols' => 'The :attribute field must contain at least one symbol.', + 'uncompromised' => 'The given :attribute has appeared in a data leak. Please choose a different :attribute.', + ], + 'present' => 'The :attribute field must be present.', + 'prohibited' => 'The :attribute field is prohibited.', + 'prohibited_if' => 'The :attribute field is prohibited when :other is :value.', + 'prohibited_unless' => 'The :attribute field is prohibited unless :other is in :values.', + 'prohibits' => 'The :attribute field prohibits :other from being present.', + 'regex' => 'The :attribute field format is invalid.', + 'required' => 'The :attribute field is required.', + 'required_array_keys' => 'The :attribute field must contain entries for: :values.', + 'required_if' => 'The :attribute field is required when :other is :value.', + 'required_if_accepted' => 'The :attribute field is required when :other is accepted.', + 'required_unless' => 'The :attribute field is required unless :other is in :values.', + 'required_with' => 'The :attribute field is required when :values is present.', + 'required_with_all' => 'The :attribute field is required when :values are present.', + 'required_without' => 'The :attribute field is required when :values is not present.', + 'required_without_all' => 'The :attribute field is required when none of :values are present.', + 'same' => 'The :attribute field must match :other.', + 'size' => [ + 'array' => 'The :attribute field must contain :size items.', + 'file' => 'The :attribute field must be :size kilobytes.', + 'numeric' => 'The :attribute field must be :size.', + 'string' => 'The :attribute field must be :size characters.', + ], + 'starts_with' => 'The :attribute field must start with one of the following: :values.', + 'string' => 'The :attribute field must be a string.', + 'timezone' => 'The :attribute field must be a valid timezone.', + 'unique' => 'The :attribute has already been taken.', + 'uploaded' => 'The :attribute failed to upload.', + 'uppercase' => 'The :attribute field must be uppercase.', + 'url' => 'The :attribute field must be a valid URL.', + 'ulid' => 'The :attribute field must be a valid ULID.', + 'uuid' => 'The :attribute field must be a valid UUID.', + + /* + |-------------------------------------------------------------------------- + | Custom Validation Language Lines + |-------------------------------------------------------------------------- + | + | Here you may specify custom validation messages for attributes using the + | convention "attribute.rule" to name the lines. This makes it quick to + | specify a specific custom language line for a given attribute rule. + | + */ + + 'custom' => [ + 'attribute-name' => [ + 'rule-name' => 'custom-message', + ], + ], + + /* + |-------------------------------------------------------------------------- + | Custom Validation Attributes + |-------------------------------------------------------------------------- + | + | The following language lines are used to swap our attribute placeholder + | with something more reader friendly such as "E-Mail Address" instead + | of "email". This simply helps us make our message more expressive. + | + */ + + 'attributes' => [ + 'phone_number' => 'phone number', + ], + +]; diff --git a/backend/resources/lang/fa/auth.php b/backend/resources/lang/fa/auth.php new file mode 100644 index 00000000..88976891 --- /dev/null +++ b/backend/resources/lang/fa/auth.php @@ -0,0 +1,20 @@ + 'اطلاعات وارد شده صحیح نمی باشد', + 'password' => 'رمزعبور صحیح نیست', + 'throttle' => 'درخواست بیش از حد مجاز! لطفا بعد از :seconds ثانیه دوباره امتحان کنید', + +]; diff --git a/backend/resources/lang/fa/messages.php b/backend/resources/lang/fa/messages.php new file mode 100644 index 00000000..ec215a9a --- /dev/null +++ b/backend/resources/lang/fa/messages.php @@ -0,0 +1,11 @@ + 'عملیات با موفقیت انجام شد.', + 'invalid_credential' => 'نام کاربری یا رمز عبور اشتباه است', + 'incorrect_current_password' => 'رمز عبور فعلی اشتباه است', + 'new_password_email_sent' => 'رمز عبور جدید به ایمیل شما ارسال شد.', + 'file_upload_failed' => 'بارگذاری فایل با خطا مواجه شد.', + 'otp' => 'کد ورود شما : ', + 'incorrect_otp' => 'کد تایید اشتباه است .', +]; diff --git a/backend/resources/lang/fa/pagination.php b/backend/resources/lang/fa/pagination.php new file mode 100644 index 00000000..e00adbf9 --- /dev/null +++ b/backend/resources/lang/fa/pagination.php @@ -0,0 +1,19 @@ + '« قبلی', + 'next' => 'بعدی »', + +]; diff --git a/backend/resources/lang/fa/passwords.php b/backend/resources/lang/fa/passwords.php new file mode 100644 index 00000000..c8081d97 --- /dev/null +++ b/backend/resources/lang/fa/passwords.php @@ -0,0 +1,22 @@ + 'رمز عبور شما با موفقیت بازیابی شد', + 'sent' => 'ایمیلی جهت بازیابی رمزعبور برای شما ارسال شد', + 'throttled' => 'لطفا اندکی صبر کنید و دوباره امتحان کنید', + 'token' => 'مشخصه بازیابی رمزعبور شما صحیح نمی باشد', + 'user' => "کاربری با این اطلاعات وجود ندارد", + +]; diff --git a/backend/resources/lang/fa/validation.php b/backend/resources/lang/fa/validation.php new file mode 100644 index 00000000..53fd3e1b --- /dev/null +++ b/backend/resources/lang/fa/validation.php @@ -0,0 +1,192 @@ + 'گزینه :attribute باید تایید شود', + 'accepted_if' => 'زمانی که گزینه :other برابر :value است :attribute باید تایید شود', + 'active_url' => 'گزینه :attribute یک آدرس سایت معتبر نیست', + 'after' => 'گزینه :attribute باید تاریخی بعد از :date باشد', + 'after_or_equal' => 'گزینه :attribute باید تاریخی مساوی یا بعد از :date باشد', + 'alpha' => 'گزینه :attribute باید تنها شامل حروف باشد', + 'alpha_dash' => 'گزینه :attribute باید تنها شامل حروف، اعداد، خط تیره و زیر خط باشد', + 'alpha_num' => 'گزینه :attribute باید تنها شامل حروف و اعداد باشد', + 'array' => 'گزینه :attribute باید آرایه باشد', + 'ascii' => 'گزینه :attribute تنها میتواند شامل تک حرف، عدد یا نماد ها باشد. .', + 'before' => 'گزینه :attribute باید تاریخی قبل از :date باشد', + 'before_or_equal' => 'گزینه :attribute باید تاریخی مساوی یا قبل از :date باشد', + 'between' => [ + 'array' => 'گزینه :attribute باید بین :min و :max آیتم باشد', + 'file' => 'گزینه :attribute باید بین :min و :max کیلوبایت باشد', + 'numeric' => 'گزینه :attribute باید بین :min و :max باشد', + 'string' => 'گزینه :attribute باید بین :min و :max کاراکتر باشد', + ], + 'boolean' => 'گزینه :attribute تنها می تواند صحیح یا غلط باشد', + 'confirmed' => 'تایید مجدد گزینه :attribute صحیح نمی باشد', + 'current_password' => 'رمزعبور صحیح نمی باشد', + 'date' => 'گزینه :attribute یک تاریخ صحیح نمی باشد', + 'date_equals' => 'گزینه :attribute باید تاریخی مساوی با :date باشد', + 'date_format' => 'گزینه :attribute با فرمت :format همخوانی ندارد', + 'decimal' => 'گزینه :attribute باید :decimal رقم اعشار داشته باشد.', + 'declined' => 'گزینه :attribute باید رد شود', + 'declined_if' => 'گزینه :attribute زمانی که :other برابر :value است باید رد شود', + 'different' => 'گزینه :attribute و :other باید متفاوت باشند', + 'digits' => 'گزینه :attribute باید :digits عدد باشد', + 'digits_between' => 'گزینه :attribute باید بین :min و :max عدد باشد', + 'dimensions' => 'ابعاد تصویر گزینه :attribute مجاز نمی باشد', + 'distinct' => 'گزینه :attribute دارای افزونگی داده می باشد', + 'doesnt_end_with' => 'گزینه :attribute نباید با این مقادیر به پایان برسد: :values.', + 'doesnt_start_with' => 'گزینه :attribute نباید با این مقادیر شروع شود: :values.', + 'email' => 'گزینه :attribute باید یک آدرس ایمیل صحیح باشد', + 'ends_with' => 'گزینه :attribute باید با یکی از این مقادیر پایان یابد، :values', + 'enum' => 'گزینه :attribute صحیح نمی باشد', + 'exists' => 'گزینه انتخاب شده :attribute صحیح نمی باشد', + 'file' => 'گزینه :attribute باید یک فایل باشد', + 'filled' => 'گزینه :attribute نمی تواند خالی باشد', + 'gt' => [ + 'array' => 'گزینه :attribute باید بیشتر از :value آیتم باشد', + 'file' => 'گزینه :attribute باید بزرگتر از :value کیلوبایت باشد', + 'numeric' => 'گزینه :attribute باید بزرگتر از :value باشد', + 'string' => 'گزینه :attribute باید بزرگتر از :value کاراکتر باشد', + ], + 'gte' => [ + 'array' => 'گزینه :attribute باید :value آیتم یا بیشتر داشته باشد', + 'file' => 'گزینه :attribute باید بزرگتر یا مساوی :value کیلوبایت باشد', + 'numeric' => 'گزینه :attribute باید بزرگتر یا مساوی :value باشد', + 'string' => 'گزینه :attribute باید بزرگتر یا مساوی :value کاراکتر باشد', + ], + 'image' => 'گزینه :attribute باید از نوع تصویر باشد', + 'in' => 'گزینه انتخابی :attribute صحیح نمی باشد', + 'in_array' => 'گزینه :attribute در :other وجود ندارد', + 'integer' => 'گزینه :attribute باید از نوع عددی باشد', + 'ip' => 'گزینه :attribute باید آی پی آدرس باشد', + 'ipv4' => 'گزینه :attribute باید آی پی آدرس ورژن 4 باشد', + 'ipv6' => 'گزینه :attribute باید آی پی آدرس ورژن 6 باشد', + 'json' => 'گزینه :attribute باید از نوع رشته جیسون باشد', + 'lowercase' => 'گزینه :attribute باید با حروف کوچک باشد.', + 'lt' => [ + 'array' => 'گزینه :attribute باید کمتر از :value آیتم داشته باشد', + 'file' => 'گزینه :attribute باید کمتر از :value کیلوبایت باشد', + 'numeric' => 'گزینه :attribute باید کمتر از :value باشد', + 'string' => 'گزینه :attribute باید کمتر از :value کاراکتر باشد', + ], + 'lte' => [ + 'array' => 'گزینه :attribute نباید کمتر از :value آیتم داشته باشد', + 'file' => 'گزینه :attribute باید مساوی یا کمتر از :value کیلوبایت باشد', + 'numeric' => 'گزینه :attribute باید مساوی یا کمتر از :value باشد', + 'string' => 'گزینه :attribute باید مساوی یا کمتر از :value کاراکتر باشد', + ], + 'mac_address' => 'گزینه :attribute باید یک مک آدرس معتبر باشد', + 'max' => [ + 'array' => 'گزینه :attribute نباید بیشتر از :max آیتم داشته باشد', + 'file' => 'گزینه :attribute نباید بزرگتر از :max کیلوبایت باشد', + 'numeric' => 'گزینه :attribute نباید بزرگتر از :max باشد', + 'string' => 'گزینه :attribute نباید بزرگتر از :max کاراکتر باشد', + ], + 'max_digits' => 'گزینه :attribute نباید بیشتر از :max رقم باشد', + 'mimes' => 'گزینه :attribute باید دارای یکی از این فرمت ها باشد: :values', + 'mimetypes' => 'گزینه :attribute باید دارای یکی از این فرمت ها باشد: :values', + 'min' => [ + 'array' => 'گزینه :attribute باید حداقل :min آیتم داشته باشد', + 'file' => 'گزینه :attribute باید حداقل :min کیلوبایت باشد', + 'numeric' => 'گزینه :attribute باید حداقل :min باشد', + 'string' => 'گزینه :attribute باید حداقل :min کاراکتر باشد', + ], + 'min_digits' => 'گزینه :attribute باید حداقل :min رقم باشد', + 'missing' => 'گزینه :attribute نباید تعریف شود.', + 'missing_if' => 'گزینه :attribute زمانی که مقدار :other برابر :value می باشد، نباید تعریف شود', + 'missing_unless' => 'گزینه :attribute نباید تعریف شود مگر اینکه گزینه :other برابر :value باشد', + 'missing_with' => 'گزینه :attribute زمانی که مقدار :values تعریف شده است دیگر نباید تعریف شود.', + 'missing_with_all' => 'گزینه :attribute زمانی که :values مقدار دارد دیگر نباید تعریف شود.', + 'multiple_of' => 'گزینه :attribute باید حاصل ضرب :value باشد', + 'not_in' => 'گزینه انتخابی :attribute صحیح نمی باشد', + 'not_regex' => 'فرمت گزینه :attribute صحیح نمی باشد', + 'numeric' => 'گزینه :attribute باید از نوع عددی باشد', + 'password' => [ + 'letters' => 'گزینه :attribute باید حداقل شامل یک حرف باشد', + 'mixed' => 'گزینه :attribute باید شامل حداقل یک حرف بزرگ و یک حرف کوچک باشد', + 'numbers' => 'گزینه :attribute باید شامل حداقل یک عدد باشد', + 'symbols' => 'گزینه :attribute باید شامل حداقل یک کارکتر خاص باشد', + 'uncompromised' => 'محتوای وارده شده در :attribute ایمن نمی باشد. لطفا گزینه :attribute را اصلاح فرمایید', + ], + 'present' => 'گزینه :attribute باید از نوع درصد باشد', + 'prohibited' => 'گزینه :attribute مجاز نمی باشد', + 'prohibited_if' => 'گزینه :attribute زمانی که :other برابر :value باشد مجاز نمی باشد', + 'prohibited_unless' => 'گزینه :attribute مجاز نیست مگر :other برابر :values باشد', + 'prohibits' => 'گزینه :attribute باعث ممنوعیت :other می باشد', + 'regex' => 'فرمت گزینه :attribute صحیح نمی باشد', + 'required' => 'تکمیل گزینه :attribute الزامی است', + 'required_array_keys' => 'گزینه :attribute باید شامل مقادیر: :values باشد', + 'required_if' => 'تکمیل گزینه :attribute زمانی که :other دارای مقدار :value است الزامی می باشد', + 'required_if_accepted' => 'تکمیل گزینه :attribute زمانی که :other انتخاب شده الزامی است', + 'required_unless' => 'تکمیل گزینه :attribute الزامی می باشد مگر :other دارای مقدار :values باشد', + 'required_with' => 'تکمیل گزینه :attribute زمانی که مقدار :values درصد است الزامی است', + 'required_with_all' => 'تکمیل گزینه :attribute زمانی که مقادیر :values درصد است الزامی می باشد', + 'required_without' => 'تکمیل گزینه :attribute زمانی که مقدار :values درصد نیست الزامی است', + 'required_without_all' => 'تکمیل گزینه :attribute زمانی که هیچ کدام از مقادیر :values درصد نیست الزامی است', + 'same' => 'گزینه های :attribute و :other باید یکی باشند', + 'size' => [ + 'array' => 'گزینه :attribute باید شامل :size آیتم باشد', + 'file' => 'گزینه :attribute باید :size کیلوبایت باشد', + 'numeric' => 'گزینه :attribute باید :size باشد', + 'string' => 'گزینه :attribute باید :size کاراکتر باشد', + ], + 'starts_with' => 'گزینه :attribute باید با یکی از این مقادیر شروع شود، :values', + 'string' => 'گزینه :attribute باید تنها شامل حروف باشد', + 'timezone' => 'گزینه :attribute باید از نوع منطقه زمانی صحیح باشد', + 'unique' => 'این :attribute از قبل ثبت شده است', + 'uploaded' => 'بارگذاری گزینه :attribute شکست خورد', + 'uppercase' => 'گزینه :attribute باید با حروف بزرگ باشد', + 'url' => 'فرمت :attribute اشتباه است', + 'ulid' => 'گزینه :attribute باید یک ULID صحیح باشد.', + 'uuid' => 'گزینه :attribute باید یک UUID صحیح باشد', + + /* + |-------------------------------------------------------------------------- + | Custom Validation Language Lines + |-------------------------------------------------------------------------- + | + | Here you may specify custom validation messages for attributes using the + | convention "attribute.rule" to name the lines. This makes it quick to + | specify a specific custom language line for a given attribute rule. + | + */ + + 'custom' => [ + 'attribute-name' => [ + 'rule-name' => 'custom-message', + ], + ], + + /* + |-------------------------------------------------------------------------- + | Custom Validation Attributes + |-------------------------------------------------------------------------- + | + | The following language lines are used to swap our attribute placeholder + | with something more reader friendly such as "E-Mail Address" instead + | of "email". This simply helps us make our message more expressive. + | + */ + + 'attributes' => [ + 'name' => 'نام', + 'username' => 'نام کاربری', + 'email' => 'ایمیل', + 'first_name' => 'نام', + 'last_name' => 'نام خانوادگی', + 'password' => 'رمز عبور', + 'password_confirmation' => 'تاییدیه رمز عبور', + ], + +]; -- 2.49.1 From f01992368e01f89efaa95881512af1158181417d Mon Sep 17 00:00:00 2001 From: faezehzafarbakhsh Date: Wed, 6 May 2026 09:12:29 +0330 Subject: [PATCH 17/61] create user info --- .../User/Controller/Auth/AuthController.php | 6 +- .../app/User/Controller/ProfileController.php | 9 +- .../User/Requests/Auth/RegisterRequest.php | 2 +- .../{Http => User}/Resources/UserResource.php | 2 +- backend/resources/lang/fa.json | 137 ------------------ backend/routes/web.php | 2 + 6 files changed, 8 insertions(+), 150 deletions(-) rename backend/app/{Http => User}/Resources/UserResource.php (93%) delete mode 100644 backend/resources/lang/fa.json diff --git a/backend/app/User/Controller/Auth/AuthController.php b/backend/app/User/Controller/Auth/AuthController.php index d8b59502..cbd4f33d 100755 --- a/backend/app/User/Controller/Auth/AuthController.php +++ b/backend/app/User/Controller/Auth/AuthController.php @@ -3,11 +3,11 @@ namespace App\User\Controller\Auth; use App\Http\Controllers\Controller; -use App\Http\Resources\UserResource; use App\Models\User; use App\Traits\ApiResponse; use App\User\Requests\Auth\LoginRequest; use App\User\Requests\Auth\RegisterRequest; +use App\User\Resources\UserResource; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; @@ -46,9 +46,7 @@ class AuthController extends Controller $user = Auth::user(); - return $this->successResponse([ - 'info' => new UserResource($user), - ]); + return $this->successResponse(new UserResource($user)); } return $this->errorResponse(__('messages.incorrect_or_username_password')); diff --git a/backend/app/User/Controller/ProfileController.php b/backend/app/User/Controller/ProfileController.php index b3aa09ce..c3c07706 100644 --- a/backend/app/User/Controller/ProfileController.php +++ b/backend/app/User/Controller/ProfileController.php @@ -3,10 +3,10 @@ namespace App\User\Controller; use App\Http\Controllers\Controller; -use App\Http\Resources\UserResource; use App\Traits\ApiResponse; use App\User\Requests\Profile\ChangePasswordRequest; use App\User\Requests\Profile\EditRequest; +use App\User\Resources\UserResource; use Illuminate\Http\JsonResponse; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Hash; @@ -17,11 +17,7 @@ class ProfileController extends Controller public function info(): JsonResponse { - $user = Auth::user(); - - return $this->successResponse([ - 'info' => new UserResource($user), - ]); + return $this->successResponse(new UserResource(Auth::user())); } public function edit(EditRequest $request): JsonResponse @@ -56,6 +52,5 @@ class ProfileController extends Controller ]); return $this->successResponse(); - } } diff --git a/backend/app/User/Requests/Auth/RegisterRequest.php b/backend/app/User/Requests/Auth/RegisterRequest.php index 97667b23..1c8055fe 100755 --- a/backend/app/User/Requests/Auth/RegisterRequest.php +++ b/backend/app/User/Requests/Auth/RegisterRequest.php @@ -24,7 +24,7 @@ class RegisterRequest extends FormRequest return [ 'username' => 'required|unique:users,username', 'email' => 'required|email|unique:users,email', - 'password' => 'required|string|regex:/^(?=.*[a-zA-Z])(?=.*\d).+$/|min:6', + 'password' => 'required|string|regex:/^(?=.*[a-zA-Z])(?=.*\d).+$/|min:6|confirmed:', ]; } } diff --git a/backend/app/Http/Resources/UserResource.php b/backend/app/User/Resources/UserResource.php similarity index 93% rename from backend/app/Http/Resources/UserResource.php rename to backend/app/User/Resources/UserResource.php index 30459058..55f98563 100755 --- a/backend/app/Http/Resources/UserResource.php +++ b/backend/app/User/Resources/UserResource.php @@ -1,6 +1,6 @@ name('login'); Route::post('logout', 'logout')->name('logout'); }); + Route::prefix('profile') ->name('profile.') + ->middleware('auth') ->controller(ProfileController::class) ->group(function () { Route::get('info', 'info')->name('info'); -- 2.49.1 From f072bd1ee8075059a0482f242318d0ad4147d255 Mon Sep 17 00:00:00 2001 From: faezehzafarbakhsh Date: Wed, 6 May 2026 09:16:35 +0330 Subject: [PATCH 18/61] delete lan --- backend/resources/lang/en/auth.php | 19 -- backend/resources/lang/en/messages.php | 7 - backend/resources/lang/en/pagination.php | 19 -- backend/resources/lang/en/passwords.php | 22 -- backend/resources/lang/en/validation.php | 151 ------------ backend/resources/lang/fa/auth.php | 19 -- backend/resources/lang/fa/messages.php | 7 - backend/resources/lang/fa/pagination.php | 19 -- backend/resources/lang/fa/passwords.php | 22 -- backend/resources/lang/fa/validation.php | 292 ----------------------- 10 files changed, 577 deletions(-) delete mode 100644 backend/resources/lang/en/auth.php delete mode 100644 backend/resources/lang/en/messages.php delete mode 100644 backend/resources/lang/en/pagination.php delete mode 100644 backend/resources/lang/en/passwords.php delete mode 100644 backend/resources/lang/en/validation.php delete mode 100644 backend/resources/lang/fa/auth.php delete mode 100644 backend/resources/lang/fa/messages.php delete mode 100644 backend/resources/lang/fa/pagination.php delete mode 100644 backend/resources/lang/fa/passwords.php delete mode 100644 backend/resources/lang/fa/validation.php diff --git a/backend/resources/lang/en/auth.php b/backend/resources/lang/en/auth.php deleted file mode 100644 index e5506df2..00000000 --- a/backend/resources/lang/en/auth.php +++ /dev/null @@ -1,19 +0,0 @@ - 'These credentials do not match our records.', - 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.', - -]; diff --git a/backend/resources/lang/en/messages.php b/backend/resources/lang/en/messages.php deleted file mode 100644 index 2d88fb58..00000000 --- a/backend/resources/lang/en/messages.php +++ /dev/null @@ -1,7 +0,0 @@ - 'The Operation was successful', - 'incorrect_current_password' => 'The current password is incorrect.', - 'incorrect_or_username_password' => 'The username or password is incorrect.', -]; diff --git a/backend/resources/lang/en/pagination.php b/backend/resources/lang/en/pagination.php deleted file mode 100644 index d4814118..00000000 --- a/backend/resources/lang/en/pagination.php +++ /dev/null @@ -1,19 +0,0 @@ - '« Previous', - 'next' => 'Next »', - -]; diff --git a/backend/resources/lang/en/passwords.php b/backend/resources/lang/en/passwords.php deleted file mode 100644 index 724de4b9..00000000 --- a/backend/resources/lang/en/passwords.php +++ /dev/null @@ -1,22 +0,0 @@ - 'Your password has been reset!', - 'sent' => 'We have e-mailed your password reset link!', - 'throttled' => 'Please wait before retrying.', - 'token' => 'This password reset token is invalid.', - 'user' => "We can't find a user with that e-mail address.", - -]; diff --git a/backend/resources/lang/en/validation.php b/backend/resources/lang/en/validation.php deleted file mode 100644 index a65914f9..00000000 --- a/backend/resources/lang/en/validation.php +++ /dev/null @@ -1,151 +0,0 @@ - 'The :attribute must be accepted.', - 'active_url' => 'The :attribute is not a valid URL.', - 'after' => 'The :attribute must be a date after :date.', - 'after_or_equal' => 'The :attribute must be a date after or equal to :date.', - 'alpha' => 'The :attribute may only contain letters.', - 'alpha_dash' => 'The :attribute may only contain letters, numbers, dashes and underscores.', - 'alpha_num' => 'The :attribute may only contain letters and numbers.', - 'array' => 'The :attribute must be an array.', - 'before' => 'The :attribute must be a date before :date.', - 'before_or_equal' => 'The :attribute must be a date before or equal to :date.', - 'between' => [ - 'numeric' => 'The :attribute must be between :min and :max.', - 'file' => 'The :attribute must be between :min and :max kilobytes.', - 'string' => 'The :attribute must be between :min and :max characters.', - 'array' => 'The :attribute must have between :min and :max items.', - ], - 'boolean' => 'The :attribute field must be true or false.', - 'confirmed' => 'The :attribute confirmation does not match.', - 'date' => 'The :attribute is not a valid date.', - 'date_equals' => 'The :attribute must be a date equal to :date.', - 'date_format' => 'The :attribute does not match the format :format.', - 'different' => 'The :attribute and :other must be different.', - 'digits' => 'The :attribute must be :digits digits.', - 'digits_between' => 'The :attribute must be between :min and :max digits.', - 'dimensions' => 'The :attribute has invalid image dimensions.', - 'distinct' => 'The :attribute field has a duplicate value.', - 'email' => 'The :attribute must be a valid email address.', - 'ends_with' => 'The :attribute must end with one of the following: :values.', - 'exists' => 'The selected :attribute is invalid.', - 'file' => 'The :attribute must be a file.', - 'filled' => 'The :attribute field must have a value.', - 'gt' => [ - 'numeric' => 'The :attribute must be greater than :value.', - 'file' => 'The :attribute must be greater than :value kilobytes.', - 'string' => 'The :attribute must be greater than :value characters.', - 'array' => 'The :attribute must have more than :value items.', - ], - 'gte' => [ - 'numeric' => 'The :attribute must be greater than or equal :value.', - 'file' => 'The :attribute must be greater than or equal :value kilobytes.', - 'string' => 'The :attribute must be greater than or equal :value characters.', - 'array' => 'The :attribute must have :value items or more.', - ], - 'image' => 'The :attribute must be an image.', - 'in' => 'The selected :attribute is invalid.', - 'in_array' => 'The :attribute field does not exist in :other.', - 'integer' => 'The :attribute must be an integer.', - 'ip' => 'The :attribute must be a valid IP address.', - 'ipv4' => 'The :attribute must be a valid IPv4 address.', - 'ipv6' => 'The :attribute must be a valid IPv6 address.', - 'json' => 'The :attribute must be a valid JSON string.', - 'lt' => [ - 'numeric' => 'The :attribute must be less than :value.', - 'file' => 'The :attribute must be less than :value kilobytes.', - 'string' => 'The :attribute must be less than :value characters.', - 'array' => 'The :attribute must have less than :value items.', - ], - 'lte' => [ - 'numeric' => 'The :attribute must be less than or equal :value.', - 'file' => 'The :attribute must be less than or equal :value kilobytes.', - 'string' => 'The :attribute must be less than or equal :value characters.', - 'array' => 'The :attribute must not have more than :value items.', - ], - 'max' => [ - 'numeric' => 'The :attribute may not be greater than :max.', - 'file' => 'The :attribute may not be greater than :max kilobytes.', - 'string' => 'The :attribute may not be greater than :max characters.', - 'array' => 'The :attribute may not have more than :max items.', - ], - 'mimes' => 'The :attribute must be a file of type: :values.', - 'mimetypes' => 'The :attribute must be a file of type: :values.', - 'min' => [ - 'numeric' => 'The :attribute must be at least :min.', - 'file' => 'The :attribute must be at least :min kilobytes.', - 'string' => 'The :attribute must be at least :min characters.', - 'array' => 'The :attribute must have at least :min items.', - ], - 'not_in' => 'The selected :attribute is invalid.', - 'not_regex' => 'The :attribute format is invalid.', - 'numeric' => 'The :attribute must be a number.', - 'password' => 'The password is incorrect.', - 'present' => 'The :attribute field must be present.', - 'regex' => 'The :attribute format is invalid.', - 'required' => 'The :attribute field is required.', - 'required_if' => 'The :attribute field is required when :other is :value.', - 'required_unless' => 'The :attribute field is required unless :other is in :values.', - 'required_with' => 'The :attribute field is required when :values is present.', - 'required_with_all' => 'The :attribute field is required when :values are present.', - 'required_without' => 'The :attribute field is required when :values is not present.', - 'required_without_all' => 'The :attribute field is required when none of :values are present.', - 'same' => 'The :attribute and :other must match.', - 'size' => [ - 'numeric' => 'The :attribute must be :size.', - 'file' => 'The :attribute must be :size kilobytes.', - 'string' => 'The :attribute must be :size characters.', - 'array' => 'The :attribute must contain :size items.', - ], - 'starts_with' => 'The :attribute must start with one of the following: :values.', - 'string' => 'The :attribute must be a string.', - 'timezone' => 'The :attribute must be a valid zone.', - 'unique' => 'The :attribute has already been taken.', - 'uploaded' => 'The :attribute failed to upload.', - 'url' => 'The :attribute format is invalid.', - 'uuid' => 'The :attribute must be a valid UUID.', - - /* - |-------------------------------------------------------------------------- - | Custom Validation Language Lines - |-------------------------------------------------------------------------- - | - | Here you may specify custom validation messages for attributes using the - | convention "attribute.rule" to name the lines. This makes it quick to - | specify a specific custom language line for a given attribute rule. - | - */ - - 'custom' => [ - 'attribute-name' => [ - 'rule-name' => 'custom-message', - ], - ], - - /* - |-------------------------------------------------------------------------- - | Custom Validation Attributes - |-------------------------------------------------------------------------- - | - | The following language lines are used to swap our attribute placeholder - | with something more reader friendly such as "E-Mail Address" instead - | of "email". This simply helps us make our message more expressive. - | - */ - - 'attributes' => [], - -]; diff --git a/backend/resources/lang/fa/auth.php b/backend/resources/lang/fa/auth.php deleted file mode 100644 index 8e208f0a..00000000 --- a/backend/resources/lang/fa/auth.php +++ /dev/null @@ -1,19 +0,0 @@ - 'اطلاعات وارد شده صحیح نمی باشد.', - 'throttle' => 'تعداد تلاش های ناموفق زیاد بود . لطفا بعد از :seconds ثانیه ی دیگر تلاش کنید .', - -]; \ No newline at end of file diff --git a/backend/resources/lang/fa/messages.php b/backend/resources/lang/fa/messages.php deleted file mode 100644 index ddab3323..00000000 --- a/backend/resources/lang/fa/messages.php +++ /dev/null @@ -1,7 +0,0 @@ - 'عملیات موفق آمیز بود', - 'incorrect_current_password' => 'رمز عبور فعلی اشتباه است', - 'incorrect_or_username_password' => 'رمز عبور یا نام کاربری اشتباه است' -]; diff --git a/backend/resources/lang/fa/pagination.php b/backend/resources/lang/fa/pagination.php deleted file mode 100644 index 0e75c41f..00000000 --- a/backend/resources/lang/fa/pagination.php +++ /dev/null @@ -1,19 +0,0 @@ - '« قبلی', - 'next' => 'بعدی »', - -]; \ No newline at end of file diff --git a/backend/resources/lang/fa/passwords.php b/backend/resources/lang/fa/passwords.php deleted file mode 100644 index c3f4fae9..00000000 --- a/backend/resources/lang/fa/passwords.php +++ /dev/null @@ -1,22 +0,0 @@ - 'رمز عبور شما با موفقیت تغییر یافت!', - 'sent' => 'لینک تغییر رمز عبور برای شما فرستاده شد!', - 'throttled' => 'لطفا قبل تلاش مجدد منتظر بمانید.', - 'token' => 'token تغییر گذر واژه (لینک) نامعتبر است.', - 'user' => "کاربری با این ایمیل یافت نشد.", - -]; \ No newline at end of file diff --git a/backend/resources/lang/fa/validation.php b/backend/resources/lang/fa/validation.php deleted file mode 100644 index d58a36b0..00000000 --- a/backend/resources/lang/fa/validation.php +++ /dev/null @@ -1,292 +0,0 @@ - ':attribute در این حالت مجاز نیست.', - "accepted" => ":attribute باید پذیرفته شده باشد.", - "active_url" => "آدرس :attribute معتبر نیست", - "after" => ":attribute باید بعد از :date باشد.", - 'after_or_equal' => ':attribute باید بعد از یا برابر تاریخ :date باشد.', - "alpha" => ":attribute باید شامل حروف الفبا باشد.", - "alpha_dash" => ":attribute باید شامل حروف الفبا و عدد و خظ تیره(-) باشد.", - "alpha_num" => ":attribute باید شامل حروف الفبا و عدد باشد.", - "array" => ":attribute باید شامل آرایه باشد.", - "before" => ":attribute باید تاریخی قبل از :date باشد.", - 'before_or_equal' => ':attribute باید قبل از یا برابر تاریخ :date باشد.', - "between" => [ - "numeric" => ":attribute باید بین :min و :max باشد.", - "file" => ":attribute باید بین :min و :max کیلوبایت باشد.", - "string" => ":attribute باید بین :min و :max کاراکتر باشد.", - "array" => ":attribute باید بین :min و :max آیتم باشد.", - ], - "boolean" => "فیلد :attribute فقط میتواند صحیح و یا غلط باشد", - "confirmed" => ":attribute با تاییدیه مطابقت ندارد.", - "date" => ":attribute یک تاریخ معتبر نیست.", - 'date_equals' => ':attribute باید برابر تاریخ :date باشد.', - "date_format" => ":attribute با الگوی :format مطاقبت ندارد.", - "different" => ":attribute و :other باید متفاوت باشند.", - "digits" => ":attribute باید :digits رقم باشد.", - "digits_between" => ":attribute باید بین :min و :max رقم باشد.", - 'dimensions' => 'dimensions مربوط به فیلد :attribute اشتباه است.', - 'distinct' => ':attribute مقدار تکراری دارد.', - "email" => "فرمت :attribute معتبر نیست.", - 'ends_with' => ':attribute باید با این مقدار تمام شود: :values.', - "exists" => ":attribute انتخاب شده، معتبر نیست.", - 'file' => 'فیلد :attribute باید فایل باشد.', - "filled" => "فیلد :attribute الزامی است", - 'gt' => [ - 'numeric' => ':attribute باید بیشتر از :value باشد.', - 'file' => ':attribute باید بیشتر از :value کیلوبایت باشد.', - 'string' => ':attribute باید بیشتر از :value کاراکتر باشد.', - 'array' => ':attribute باید بیشتر از :value ایتم باشد.', - ], - 'gte' => [ - 'numeric' => ':attribute باید بیشتر یا برابر :value باشد.', - 'file' => ':attribute باید بیشتر یا برابر :value کیلوبایت باشد.', - 'string' => ':attribute باید بیشتر یا برابر :value کاراکتر باشد.', - 'array' => ':attribute باید :value ایتم یا بیشتر را داشته باشد.', - ], - "image" => ":attribute باید تصویر باشد.", - "in" => ":attribute انتخاب شده، معتبر نیست.", - "integer" => ":attribute باید نوع داده ای عددی (integer) باشد.", - "ip" => ":attribute باید IP آدرس معتبر باشد.", - 'ipv4' => ':attribute باید یک ادرس درست IPv4 باشد.', - 'ipv6' => ':attribute باید یک ادرس درست IPv6 باشد.', - 'json' => ':attribute یک مقدار درست JSON باشد.', - 'lt' => [ - 'numeric' => ':attribute باید کمتر از :value باشد.', - 'file' => ':attribute باید کمتر از :value کیلوبایت باشد.', - 'string' => ':attribute باید کمتر از :value کاراکتر باشد.', - 'array' => ':attribute باید :value ایتم یا کمتر را داشته باشد.', - ], - 'lte' => [ - 'numeric' => ':attribute باید کمتر یا برابر :value باشد.', - 'file' => ':attribute باید کمتر یا برابر :value کیلوبایت باشد.', - 'string' => ':attribute باید کمتر یا برابر :value کاراکتر باشد.', - 'array' => ':attribute باید :value ایتم یا کمتر را داشته باشد.', - ], - "max" => [ - "numeric" => ":attribute نباید بزرگتر از :max باشد.", - "file" => ":attribute نباید بزرگتر از :max کیلوبایت باشد.", - "string" => ":attribute نباید بیشتر از :max کاراکتر باشد.", - "array" => ":attribute نباید بیشتر از :max آیتم باشد.", - ], - "mimes" => ":attribute باید یکی از فرمت های :values باشد.", - 'mimetypes' => ':attribute باید تایپ ان از نوع: :values باشد.', - "min" => [ - "numeric" => ":attribute نباید کوچکتر از :min باشد.", - "file" => ":attribute نباید کوچکتر از :min کیلوبایت باشد.", - "string" => ":attribute نباید کمتر از :min کاراکتر باشد.", - "array" => ":attribute نباید کمتر از :min آیتم باشد.", - ], - "not_in" => ":attribute انتخاب شده، معتبر نیست.", - 'not_regex' => ':attribute فرمت معتبر نیست.', - "numeric" => ":attribute باید شامل عدد باشد.", - 'password' => 'رمز عبور اشتباه است.', - 'present' => ':attribute باید وجود داشته باشد.', - "regex" => ":attribute یک فرمت معتبر نیست", - "required" => "فیلد :attribute الزامی است", - "required_if" => "فیلد :attribute هنگامی که :other برابر با :value است، الزامیست.", - 'required_unless' => 'قیلد :attribute الزامیست مگر این فیلد :other مقدارش :values باشد.', - "required_with" => ":attribute الزامی است زمانی که :values موجود است.", - "required_with_all" => ":attribute الزامی است زمانی که :values موجود است.", - "required_without" => ":attribute الزامی است زمانی که :values موجود نیست.", - "required_without_all" => ":attribute الزامی است زمانی که :values موجود نیست.", - "same" => ":attribute و :other باید مانند هم باشند.", - "size" => [ - "numeric" => ":attribute باید برابر با :size باشد.", - "file" => ":attribute باید برابر با :size کیلوبایت باشد.", - "string" => ":attribute باید برابر با :size کاراکتر باشد.", - "array" => ":attribute باسد شامل :size آیتم باشد.", - ], - 'starts_with' => ':attribute باید با یکی از این مقادیر شروع شود: :values.', - "string" => ":attribute باید رشته باشد.", - "timezone" => "فیلد :attribute باید یک منطقه صحیح باشد.", - "unique" => ":attribute قبلا انتخاب شده است.", - 'uploaded' => 'فیلد :attribute به درستی اپلود نشد.', - "url" => "فرمت آدرس :attribute اشتباه است.", - 'uuid' => ':attribute باید یک فرمت درست UUID باشد.', - - /* - |-------------------------------------------------------------------------- - | Custom Validation Language Lines - |-------------------------------------------------------------------------- - | - | Here you may specify custom validation messages for attributes using the - | convention "attribute.rule" to name the lines. This makes it quick to - | specify a specific custom language line for a given attribute rule. - | - */ - - 'custom' => [], - - /* - |-------------------------------------------------------------------------- - | Custom Validation Attributes - |-------------------------------------------------------------------------- - | - | The following language lines are used to swap attribute place-holders - | with something more reader friendly such as E-Mail Address instead - | of "email". This simply helps us make messages a little cleaner. - | - */ - 'attributes' => [ - "name" => "نام", - "username" => "نام کاربری", - "national_code" => "کد ملی", - "email" => "پست الکترونیکی", - "first_name" => "نام", - "last_name" => "نام خانوادگی", - "family" => "نام خانوادگی", - "password" => "رمز عبور", - "password_confirmation" => "تاییدیه ی رمز عبور", - "city" => "شهر", - "country" => "کشور", - "address" => "نشانی", - "phone" => "تلفن", - "mobile" => "تلفن همراه", - "age" => "سن", - "sex" => "جنسیت", - "gender" => "جنسیت", - "day" => "روز", - "month" => "ماه", - "year" => "سال", - "hour" => "ساعت", - "minute" => "دقیقه", - "second" => "ثانیه", - "title" => "عنوان", - "text" => "متن", - "content" => "محتوا", - "description" => "توضیحات", - "excerpt" => "گلچین کردن", - "date" => "تاریخ", - "time" => "زمان", - "available" => "موجود", - "size" => "اندازه", - "file" => "فایل", - "fullname" => "نام کامل", - 'current_password' => 'رمز عبور فعلی', - 'new_password' => 'رمز عبور جدید', - 'degree' => 'مدرک تحصیلی', - 'major' => 'شغل', - 'avatar' => 'آواتار', - 'lat' => 'طول جغرافیایی', - 'lng' => 'عرض جغرافیایی', - 'azmayesh_type_id' => 'نوع آزمایش', - 'request_date' => 'تاریخ درخواست', - 'report_date' => 'تاریخ گزارش', - 'request_number' => 'شماره درخواست', - 'employer' => 'کارفرما', - 'contractor' => 'پیمانکار', - 'work_number' => 'شماره کار', - 'project_name' => 'نام پروژه', - 'consultant' => 'مشاور', - 'applicant' => 'متقاضی', - 'province_id' => 'استان', - 'azmayesh_id' => 'آزمایش', - 'data' => 'داده', - 'fields' => 'فیلد ها', - 'fields.*.name' => 'نام فیلد', - 'fields.*.unit' => 'واحد فیلد', - 'fields.*.type' => 'نوع فیلد', - 'fields.*.option' => 'گزینه های فیلد', - 'cmms_machine_id' => 'کد یکتا ماشین', - 'contract_subitem_id' => 'کد یکتای پروژه', - 'base_price' => 'قیمت پایه', - 'unit' => 'واحد', - 'phone_number'=> 'شماره تلفن', - 'final_description' => 'توضیحات نهایی', - 'point' => 'منطقه' , - 'info_id' => 'دسته بندی' , - 'activity_date' => 'تاریخ فعالیت', - 'activity_time' => 'زمان فعالیت', - 'recognize_picture' => 'عکس بازدید', - 'recognize_picture_second' => 'عکس بازدید', - 'axis_type_id' => 'نوع محور', - 'supervisor_description' => 'توضیحات کارشناس', - 'need_judiciary' => 'دستور قضایی' , - 'operator_description' => 'توضیحات ناظر', - 'judiciary_document' => 'فایل دستور قضایی', - 'action_picture' => 'عکس اقدامات', - 'finish_picture' => 'عکس پایان کار', - 'action_date' => 'زمان فالیت', - 'evidence_picture' => 'تصویر مشاهده شده', - 'deposit_insurance_image' => 'عکس مبلغ بیمه', - 'deposit_insurance_amount' => ' مبلغ بیمه', - 'deposit_daghi_image' => 'عکس مبلغ بیمه', - 'deposit_daghi_amount' => 'مبلغ داغی', - 'is_foreign' => 'ناوگان خارجی', - 'axis_name' => 'نام محور', - 'driver_name' => 'اسم راننده', - 'plaque' => 'پلاک', - 'driver_national_code' => 'کدملی راننده', - 'driver_phone_number' => 'شماره موبایل راننده', - 'accident_type' => 'نوع خسارت', - 'accident_date' => 'تاریخ تصادف', - 'accident_time' => 'زمان تصادف', - 'report_base' => 'گزارش', - 'police_file' => 'تصویر کروکی یا نامه پلیس راه', - 'police_serial' => 'شماره کروکی یا نامه پلیس راه', - 'police_file_date' => 'تاریخ کروکی یا نامه پلیس راه', - 'damage_picture1' => 'عکس تصادف', - 'damage_picture2' => 'عکس تصادف', - 'damage_items' => 'ایتم های خسارت', - 'damage_items.*.value' => 'میزان ایتم های خسارت', - 'damage_items.*.amount' => 'هزینه خسارت', - 'driver_rate' => 'سهم راننده', - 'code' => 'کد', - 'rahdaran' => 'افراد', - 'machines' => 'خودرو', - 'driver' => 'راننده', - 'zone' => 'منطقه', - 'start_date' => 'تاریخ شروع', - 'end_date' => 'تاریخ پایان', - 'end_point' => 'مقصد', - 'area' => 'منطقه عملیاتی', - 'area.type' => 'نوع محدوده' , - 'area.coordinates' => 'مختصات محدوده', - 'category_id' => 'دسته بندی', - 'explanation' => 'توضیحات', - 'requested_machines' => 'ماشین های درخواستی', - 'type' => 'نوع', - 'road_observed_id' => 'کد واکنش سریع', - 'city_id' => 'شهر', - 'expert_description' => 'توضیح کارشناس', - 'need_payment' => 'نیاز به پرداخت', - 'request_id' => 'شناسه درخواست', - 'access_road' => 'راه دسترسی', - 'access_road.type' => 'نوع راه دسترسی', - 'access_road.coordinates' => 'مختصات راه دسترسی', - 'final_area' => 'محدوده نهایی', - 'final_area.type' => 'نوع محدوده نهایی', - 'final_area.coordinates' => 'مختصات محدوده نهایی', - 'final_plan' => 'طرح نهایی', - 'final_plan.type' => 'نوع طرح نهایی', - 'final_plan.coordinate' => 'مختصات طرح نهایی', - 'national_id' => 'کد ملی' , - 'requested_organization' => 'سازمان درخواست‌کننده', - 'plan_group' => 'گروه طرح', - 'plan_title' => 'موضوع طرح', - 'worksheet' => 'شناسه کاربرگ', - 'response_options' => 'گزینه های پاسخ', - 'isic' => 'کد ISIC', - 'primary_area' => 'محدوده اولیه', - 'primary_area.type' => 'نوع محدوده اولیه', - 'primary_area.coordinates' => 'مختصات محدوده اولیه', - 'forbidden_area' => 'محدوده ممنوعه', - 'forbidden_area.type' => 'نوع محدوده محدوده', - 'forbidden_area.coordinates' => 'مختصات محدوده ممنوعه', - 'edarate_ostani_id' => 'اداره استانی', - 'edarate_shahri_id' => ' اداره شهری' - ], -]; -- 2.49.1 From c2328ec666096534b693cd095ae533e367539d9a Mon Sep 17 00:00:00 2001 From: amirghasempoor Date: Sat, 9 May 2026 09:09:49 +0330 Subject: [PATCH 19/61] make auth service global --- .../Admin/Controllers/ProfileController.php | 19 +++++++++++++++ .../app/Admin/Resources/ExpertResource.php | 24 +++++++++++++++++++ .../Controllers}/AuthController.php | 14 ++++++----- .../Requests/Auth/LoginRequest.php | 5 ++-- .../Requests/Auth/RegisterRequest.php | 5 ++-- backend/app/User/Resources/UserResource.php | 0 backend/routes/api.php | 2 -- backend/routes/web.php | 2 +- 8 files changed, 58 insertions(+), 13 deletions(-) create mode 100644 backend/app/Admin/Controllers/ProfileController.php create mode 100644 backend/app/Admin/Resources/ExpertResource.php rename backend/app/{User/Controller/Auth => Http/Controllers}/AuthController.php (82%) mode change 100755 => 100644 rename backend/app/{User => Http}/Requests/Auth/LoginRequest.php (77%) mode change 100755 => 100644 rename backend/app/{User => Http}/Requests/Auth/RegisterRequest.php (81%) mode change 100755 => 100644 mode change 100755 => 100644 backend/app/User/Resources/UserResource.php diff --git a/backend/app/Admin/Controllers/ProfileController.php b/backend/app/Admin/Controllers/ProfileController.php new file mode 100644 index 00000000..39d81669 --- /dev/null +++ b/backend/app/Admin/Controllers/ProfileController.php @@ -0,0 +1,19 @@ +successResponse(new ExpertResource(Auth::user())); + } +} diff --git a/backend/app/Admin/Resources/ExpertResource.php b/backend/app/Admin/Resources/ExpertResource.php new file mode 100644 index 00000000..b7c86e1c --- /dev/null +++ b/backend/app/Admin/Resources/ExpertResource.php @@ -0,0 +1,24 @@ + + */ + public function toArray(Request $request): array + { + return [ + 'id' => $this->id, + 'username' => $this->username, + 'email' => $this->email, + ]; + } +} + diff --git a/backend/app/User/Controller/Auth/AuthController.php b/backend/app/Http/Controllers/AuthController.php old mode 100755 new mode 100644 similarity index 82% rename from backend/app/User/Controller/Auth/AuthController.php rename to backend/app/Http/Controllers/AuthController.php index cbd4f33d..26598504 --- a/backend/app/User/Controller/Auth/AuthController.php +++ b/backend/app/Http/Controllers/AuthController.php @@ -1,13 +1,11 @@ successResponse(new UserResource($user)); + return $this->successResponse([ + 'id' => $user->id, + 'username' => $user->username, + 'email' => $user->email, + ]); } return $this->errorResponse(__('messages.incorrect_or_username_password')); diff --git a/backend/app/User/Requests/Auth/LoginRequest.php b/backend/app/Http/Requests/Auth/LoginRequest.php old mode 100755 new mode 100644 similarity index 77% rename from backend/app/User/Requests/Auth/LoginRequest.php rename to backend/app/Http/Requests/Auth/LoginRequest.php index d9b9c881..592e8920 --- a/backend/app/User/Requests/Auth/LoginRequest.php +++ b/backend/app/Http/Requests/Auth/LoginRequest.php @@ -1,7 +1,8 @@ + * @return array */ public function rules(): array { diff --git a/backend/app/User/Requests/Auth/RegisterRequest.php b/backend/app/Http/Requests/Auth/RegisterRequest.php old mode 100755 new mode 100644 similarity index 81% rename from backend/app/User/Requests/Auth/RegisterRequest.php rename to backend/app/Http/Requests/Auth/RegisterRequest.php index 1c8055fe..9c7e9a84 --- a/backend/app/User/Requests/Auth/RegisterRequest.php +++ b/backend/app/Http/Requests/Auth/RegisterRequest.php @@ -1,7 +1,8 @@ + * @return array */ public function rules(): array { diff --git a/backend/app/User/Resources/UserResource.php b/backend/app/User/Resources/UserResource.php old mode 100755 new mode 100644 diff --git a/backend/routes/api.php b/backend/routes/api.php index 56897756..f35f6f84 100755 --- a/backend/routes/api.php +++ b/backend/routes/api.php @@ -1,10 +1,8 @@ user(); })->middleware('auth:api'); - diff --git a/backend/routes/web.php b/backend/routes/web.php index 8dc5599e..65362937 100755 --- a/backend/routes/web.php +++ b/backend/routes/web.php @@ -1,6 +1,6 @@ Date: Sun, 10 May 2026 14:25:12 +0330 Subject: [PATCH 20/61] prevent request forgery --- backend/Dockerfile | 1 - backend/bootstrap/app.php | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/backend/Dockerfile b/backend/Dockerfile index 3a24367a..6b930093 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -30,7 +30,6 @@ ENV COMPOSER_ALLOW_SUPERUSER=1 # Default command CMD composer install ;\ composer dump-autoload --optimize --classmap-authoritative ;\ - php artisan key:generate ;\ php artisan migrate ;\ php artisan db:seed ;\ php-fpm diff --git a/backend/bootstrap/app.php b/backend/bootstrap/app.php index 6de85c67..68a86469 100755 --- a/backend/bootstrap/app.php +++ b/backend/bootstrap/app.php @@ -12,7 +12,7 @@ return Application::configure(basePath: dirname(__DIR__)) health: '/up', ) ->withMiddleware(function (Middleware $middleware): void { - $middleware->web()->validateCsrfTokens(['*']); + $middleware->web()->preventRequestForgery(['*']); }) ->withExceptions(function (Exceptions $exceptions): void { // -- 2.49.1 From 4afcd5c81e3a55fd9aa6f7918e3729e342502b91 Mon Sep 17 00:00:00 2001 From: faezehzafarbakhsh Date: Sun, 10 May 2026 14:29:52 +0330 Subject: [PATCH 21/61] create EmailEvent for login --- backend/app/Mail/LoginNotificationMail.php | 40 +++++++++++++++++++ .../Dashboard/ConfirmController.php | 14 +++++++ .../app/User/Controller/ProfileController.php | 2 +- backend/app/User/Events/LoginEmailEvent.php | 23 +++++++++++ .../app/User/Listeners/SendEmailListener.php | 31 ++++++++++++++ .../app/User/Requests/Profile/EditRequest.php | 3 +- backend/bootstrap/app.php | 5 ++- .../views/emails/login-notification.blade.php | 24 +++++++++++ 8 files changed, 138 insertions(+), 4 deletions(-) create mode 100644 backend/app/Mail/LoginNotificationMail.php create mode 100755 backend/app/User/Controller/Dashboard/ConfirmController.php create mode 100755 backend/app/User/Events/LoginEmailEvent.php create mode 100755 backend/app/User/Listeners/SendEmailListener.php create mode 100644 backend/resources/views/emails/login-notification.blade.php diff --git a/backend/app/Mail/LoginNotificationMail.php b/backend/app/Mail/LoginNotificationMail.php new file mode 100644 index 00000000..809d46da --- /dev/null +++ b/backend/app/Mail/LoginNotificationMail.php @@ -0,0 +1,40 @@ + $request->national_id, 'address' => $request->address, 'postal_code' => $request->postal_code, - 'account_number' => $request->account_number, 'phone_number' => $request->phone_number, 'province_id' => $request->province_id, 'city_id' => $request->city_id, + 'status' => 1 ]); return $this->successResponse(); diff --git a/backend/app/User/Events/LoginEmailEvent.php b/backend/app/User/Events/LoginEmailEvent.php new file mode 100755 index 00000000..f3199341 --- /dev/null +++ b/backend/app/User/Events/LoginEmailEvent.php @@ -0,0 +1,23 @@ +user; + + Mail::to($user->email)->send( + new LoginNotificationMail($user) + ); + } +} diff --git a/backend/app/User/Requests/Profile/EditRequest.php b/backend/app/User/Requests/Profile/EditRequest.php index ddcfcba8..4eb93a4e 100644 --- a/backend/app/User/Requests/Profile/EditRequest.php +++ b/backend/app/User/Requests/Profile/EditRequest.php @@ -28,8 +28,7 @@ class EditRequest extends FormRequest 'national_id' => 'required', 'address' => 'required', 'postal_code' => 'required', - 'account_number' => 'required', - 'phone_number' => 'required|regex:/^964\d{9}$/|numeric|digits:11', + 'phone_number' => 'required', 'province_id' => 'required', 'city_id' => 'required', ]; diff --git a/backend/bootstrap/app.php b/backend/bootstrap/app.php index 6de85c67..f49ae176 100755 --- a/backend/bootstrap/app.php +++ b/backend/bootstrap/app.php @@ -16,4 +16,7 @@ return Application::configure(basePath: dirname(__DIR__)) }) ->withExceptions(function (Exceptions $exceptions): void { // - })->create(); + }) + ->withEvents(discover: [ + __DIR__.'/../app/User/Listeners', + ])->create(); diff --git a/backend/resources/views/emails/login-notification.blade.php b/backend/resources/views/emails/login-notification.blade.php new file mode 100644 index 00000000..035c3c5c --- /dev/null +++ b/backend/resources/views/emails/login-notification.blade.php @@ -0,0 +1,24 @@ + + + + + Login Notification + + +

Hello {{ $user->username }}

+ +

You have successfully logged in to your account.

+ +

+ Email: {{ $user->email }} +

+ +

+ If this login was not done by you, please contact support immediately. +

+ +
+ +

Thank you.

+ + -- 2.49.1 From e3831e98c99df0751c2472f3155b41626801f18c Mon Sep 17 00:00:00 2001 From: amirghasempoor Date: Tue, 12 May 2026 11:08:47 +0330 Subject: [PATCH 22/61] create city and province resources --- .../app/Admin/Controllers/AuthController.php | 60 +++++++++++++++++++ .../Controllers/DocumentationController.php | 46 ++++++++++++++ backend/app/Models/City.php | 12 ++++ backend/app/Models/Document.php | 12 ++++ backend/app/Models/Province.php | 12 ++++ .../Controller}/AuthController.php | 7 ++- .../Requests/Auth/LoginRequest.php | 2 +- .../Requests/Auth/RegisterRequest.php | 4 +- backend/config/auth.php | 8 +++ backend/database/factories/CityFactory.php | 24 ++++++++ .../database/factories/DocumentFactory.php | 24 ++++++++ .../database/factories/ProvinceFactory.php | 24 ++++++++ ...01_01_00_000000_create_provinces_table.php | 28 +++++++++ .../0001_01_00_000001_create_cities_table.php | 29 +++++++++ .../0001_01_01_000000_create_users_table.php | 9 +-- ...26_05_12_053258_create_documents_table.php | 30 ++++++++++ backend/database/seeders/CitySeeder.php | 17 ++++++ backend/database/seeders/DocumentSeeder.php | 17 ++++++ backend/database/seeders/ProvinceSeeder.php | 17 ++++++ backend/routes/web.php | 2 +- 20 files changed, 373 insertions(+), 11 deletions(-) create mode 100644 backend/app/Admin/Controllers/AuthController.php create mode 100644 backend/app/Admin/Controllers/DocumentationController.php create mode 100644 backend/app/Models/City.php create mode 100644 backend/app/Models/Document.php create mode 100644 backend/app/Models/Province.php rename backend/app/{Http/Controllers => User/Controller}/AuthController.php (91%) rename backend/app/{Http => User}/Requests/Auth/LoginRequest.php (94%) rename backend/app/{Http => User}/Requests/Auth/RegisterRequest.php (91%) create mode 100644 backend/database/factories/CityFactory.php create mode 100644 backend/database/factories/DocumentFactory.php create mode 100644 backend/database/factories/ProvinceFactory.php create mode 100644 backend/database/migrations/0001_01_00_000000_create_provinces_table.php create mode 100644 backend/database/migrations/0001_01_00_000001_create_cities_table.php create mode 100644 backend/database/migrations/2026_05_12_053258_create_documents_table.php create mode 100644 backend/database/seeders/CitySeeder.php create mode 100644 backend/database/seeders/DocumentSeeder.php create mode 100644 backend/database/seeders/ProvinceSeeder.php diff --git a/backend/app/Admin/Controllers/AuthController.php b/backend/app/Admin/Controllers/AuthController.php new file mode 100644 index 00000000..5c794ff5 --- /dev/null +++ b/backend/app/Admin/Controllers/AuthController.php @@ -0,0 +1,60 @@ +create(); + + $request->session()->regenerate(); + + return $this->successResponse(); + } + + public function login(Request $request): JsonResponse + { + $credentials = ['password' => $request->password]; + + if (filter_var($request->login, FILTER_VALIDATE_EMAIL)) { + $credentials['email'] = $request->login; + } else { + $credentials['username'] = $request->login; + } + + if (Auth::attempt($credentials)) + { + $request->session()->regenerate(); + + $user = Auth::user(); + + return $this->successResponse([ + 'id' => $user->id, + 'username' => $user->username, + 'email' => $user->email, + ]); + } + + return $this->errorResponse(__('messages.incorrect_or_username_password')); + } + + public function logout(Request $request): JsonResponse + { + Auth::logout(); + + $request->session()->invalidate(); + + $request->session()->regenerateToken(); + + return $this->successResponse(); + } +} diff --git a/backend/app/Admin/Controllers/DocumentationController.php b/backend/app/Admin/Controllers/DocumentationController.php new file mode 100644 index 00000000..bfcf82bf --- /dev/null +++ b/backend/app/Admin/Controllers/DocumentationController.php @@ -0,0 +1,46 @@ +with('documentations'), + $request, + allowedFilters: ['*'], + allowedSortings: ['*'], + allowedSelects: [ + 'user.id', 'user.username' + ] + ); + } + + public function confirm(User $user): JsonResponse + { + $user->update([ + + ]); + + return $this->successResponse(); + } + + public function reject(User $user): JsonResponse + { + $user->update([ + + ]); + + return $this->successResponse(); + } +} diff --git a/backend/app/Models/City.php b/backend/app/Models/City.php new file mode 100644 index 00000000..3f3ec292 --- /dev/null +++ b/backend/app/Models/City.php @@ -0,0 +1,12 @@ + */ + use HasFactory; +} diff --git a/backend/app/Models/Document.php b/backend/app/Models/Document.php new file mode 100644 index 00000000..b2413440 --- /dev/null +++ b/backend/app/Models/Document.php @@ -0,0 +1,12 @@ + */ + use HasFactory; +} diff --git a/backend/app/Models/Province.php b/backend/app/Models/Province.php new file mode 100644 index 00000000..115498c4 --- /dev/null +++ b/backend/app/Models/Province.php @@ -0,0 +1,12 @@ + */ + use HasFactory; +} diff --git a/backend/app/Http/Controllers/AuthController.php b/backend/app/User/Controller/AuthController.php similarity index 91% rename from backend/app/Http/Controllers/AuthController.php rename to backend/app/User/Controller/AuthController.php index 26598504..8fc61c9b 100644 --- a/backend/app/Http/Controllers/AuthController.php +++ b/backend/app/User/Controller/AuthController.php @@ -1,11 +1,12 @@ 'required|unique:users,username', 'email' => 'required|email|unique:users,email', - 'password' => 'required|string|regex:/^(?=.*[a-zA-Z])(?=.*\d).+$/|min:6|confirmed:', + 'password' => 'required|string|regex:/^(?=.*[a-zA-Z])(?=.*\d).+$/|min:6|confirmed', ]; } } diff --git a/backend/config/auth.php b/backend/config/auth.php index d0c87999..a968e457 100755 --- a/backend/config/auth.php +++ b/backend/config/auth.php @@ -46,6 +46,10 @@ return [ 'driver' => 'passport', 'provider' => 'users', ], + 'expert' => [ + 'driver' => 'session', + 'provider' => 'experts', + ] ], /* @@ -70,6 +74,10 @@ return [ 'driver' => 'eloquent', 'model' => env('AUTH_MODEL', User::class), ], + 'experts' => [ + 'driver' => 'eloquent', + 'model' => env('AUTH_MODEL', Expert::class), + ], // 'users' => [ // 'driver' => 'database', diff --git a/backend/database/factories/CityFactory.php b/backend/database/factories/CityFactory.php new file mode 100644 index 00000000..e658dee8 --- /dev/null +++ b/backend/database/factories/CityFactory.php @@ -0,0 +1,24 @@ + + */ +class CityFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + // + ]; + } +} diff --git a/backend/database/factories/DocumentFactory.php b/backend/database/factories/DocumentFactory.php new file mode 100644 index 00000000..bb26de44 --- /dev/null +++ b/backend/database/factories/DocumentFactory.php @@ -0,0 +1,24 @@ + + */ +class DocumentFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + // + ]; + } +} diff --git a/backend/database/factories/ProvinceFactory.php b/backend/database/factories/ProvinceFactory.php new file mode 100644 index 00000000..3b869034 --- /dev/null +++ b/backend/database/factories/ProvinceFactory.php @@ -0,0 +1,24 @@ + + */ +class ProvinceFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + // + ]; + } +} diff --git a/backend/database/migrations/0001_01_00_000000_create_provinces_table.php b/backend/database/migrations/0001_01_00_000000_create_provinces_table.php new file mode 100644 index 00000000..3a3f6901 --- /dev/null +++ b/backend/database/migrations/0001_01_00_000000_create_provinces_table.php @@ -0,0 +1,28 @@ +id(); + $table->string('name'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('provinces'); + } +}; diff --git a/backend/database/migrations/0001_01_00_000001_create_cities_table.php b/backend/database/migrations/0001_01_00_000001_create_cities_table.php new file mode 100644 index 00000000..4cf721fc --- /dev/null +++ b/backend/database/migrations/0001_01_00_000001_create_cities_table.php @@ -0,0 +1,29 @@ +id(); + $table->string('name'); + $table->foreignId('province_id')->constrained('provinces'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('cities'); + } +}; diff --git a/backend/database/migrations/0001_01_01_000000_create_users_table.php b/backend/database/migrations/0001_01_01_000000_create_users_table.php index 87b84e12..faeaec9b 100755 --- a/backend/database/migrations/0001_01_01_000000_create_users_table.php +++ b/backend/database/migrations/0001_01_01_000000_create_users_table.php @@ -18,15 +18,16 @@ return new class extends Migration $table->string('last_name')->nullable(); $table->string('email')->unique(); $table->string('password'); - $table->tinyInteger('status')->default(0); + $table->tinyInteger('kyc_status')->default(0); $table->string('national_id')->nullable(); $table->string('address')->nullable(); $table->string('postal_code')->nullable(); - $table->string('account_number')->nullable(); + $table->string('tax_id')->nullable(); $table->string('phone_number')->nullable(); - $table->string('province_id')->nullable(); + $table->date('birth_date')->nullable(); + $table->foreignId('province_id')->nullable()->constrained('provinces'); $table->string('province_name')->nullable(); - $table->string('city_id')->nullable(); + $table->foreignId('city_id')->nullable()->constrained('cities'); $table->string('city_name')->nullable(); $table->timestamps(); diff --git a/backend/database/migrations/2026_05_12_053258_create_documents_table.php b/backend/database/migrations/2026_05_12_053258_create_documents_table.php new file mode 100644 index 00000000..8bcc6ba1 --- /dev/null +++ b/backend/database/migrations/2026_05_12_053258_create_documents_table.php @@ -0,0 +1,30 @@ +id(); + $table->morphs('documentable'); + $table->string('title'); + $table->string('url'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('documents'); + } +}; diff --git a/backend/database/seeders/CitySeeder.php b/backend/database/seeders/CitySeeder.php new file mode 100644 index 00000000..670c9643 --- /dev/null +++ b/backend/database/seeders/CitySeeder.php @@ -0,0 +1,17 @@ + Date: Tue, 12 May 2026 13:04:15 +0330 Subject: [PATCH 23/61] create user documents --- .../Controllers/DocumentationController.php | 6 ++- backend/app/Models/City.php | 5 ++- backend/app/Models/Document.php | 20 +++++++++- backend/app/Models/Expert.php | 15 +++++++ backend/app/Models/Province.php | 5 ++- backend/app/Models/User.php | 6 +++ backend/database/factories/ExpertFactory.php | 24 ++++++++++++ .../0001_01_01_000000_create_users_table.php | 1 - ...2026_05_12_074230_create_experts_table.php | 39 +++++++++++++++++++ backend/database/seeders/ExpertSeeder.php | 17 ++++++++ 10 files changed, 132 insertions(+), 6 deletions(-) create mode 100644 backend/app/Models/Expert.php create mode 100644 backend/database/factories/ExpertFactory.php create mode 100644 backend/database/migrations/2026_05_12_074230_create_experts_table.php create mode 100644 backend/database/seeders/ExpertSeeder.php diff --git a/backend/app/Admin/Controllers/DocumentationController.php b/backend/app/Admin/Controllers/DocumentationController.php index bfcf82bf..357c7b21 100644 --- a/backend/app/Admin/Controllers/DocumentationController.php +++ b/backend/app/Admin/Controllers/DocumentationController.php @@ -16,12 +16,14 @@ class DocumentationController extends Controller public function index(Request $request): array { return DataTableFacade::run( - User::query()->with('documentations'), + User::query()->with('documents'), $request, allowedFilters: ['*'], allowedSortings: ['*'], allowedSelects: [ - 'user.id', 'user.username' + 'users.id', 'users.first_name', 'users.last_name', 'users.national_id', + 'users.phone_number', 'users.postal_code', 'users.city_id', 'users.province_id', + 'users.province_name', 'users.city_name', 'users.address', 'documents.title', 'documents.url' ] ); } diff --git a/backend/app/Models/City.php b/backend/app/Models/City.php index 3f3ec292..445e69c9 100644 --- a/backend/app/Models/City.php +++ b/backend/app/Models/City.php @@ -2,11 +2,14 @@ namespace App\Models; +use Database\Factories\CityFactory; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class City extends Model { - /** @use HasFactory<\Database\Factories\CityFactory> */ + /** @use HasFactory */ use HasFactory; + + protected $guarded = ['id']; } diff --git a/backend/app/Models/Document.php b/backend/app/Models/Document.php index b2413440..02d0d2f3 100644 --- a/backend/app/Models/Document.php +++ b/backend/app/Models/Document.php @@ -2,11 +2,29 @@ namespace App\Models; +use Database\Factories\DocumentFactory; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\MorphTo; +use Illuminate\Database\Eloquent\Casts\Attribute; +use Illuminate\Support\Facades\Storage; class Document extends Model { - /** @use HasFactory<\Database\Factories\DocumentFactory> */ + /** @use HasFactory */ use HasFactory; + + protected $guarded = ['id']; + + public function documentable(): MorphTo + { + return $this->morphTo(); + } + + protected function url(): Attribute + { + return Attribute::make( + get: fn($value) => Storage::disk('public')->url($value) + ); + } } diff --git a/backend/app/Models/Expert.php b/backend/app/Models/Expert.php new file mode 100644 index 00000000..214bb797 --- /dev/null +++ b/backend/app/Models/Expert.php @@ -0,0 +1,15 @@ + */ + use HasFactory; + + protected $guarded = ['id']; +} diff --git a/backend/app/Models/Province.php b/backend/app/Models/Province.php index 115498c4..854975a8 100644 --- a/backend/app/Models/Province.php +++ b/backend/app/Models/Province.php @@ -2,11 +2,14 @@ namespace App\Models; +use Database\Factories\ProvinceFactory; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class Province extends Model { - /** @use HasFactory<\Database\Factories\ProvinceFactory> */ + /** @use HasFactory */ use HasFactory; + + protected $guarded = ['id']; } diff --git a/backend/app/Models/User.php b/backend/app/Models/User.php index 9f73d3cc..2d179582 100755 --- a/backend/app/Models/User.php +++ b/backend/app/Models/User.php @@ -8,6 +8,7 @@ use Illuminate\Database\Eloquent\Attributes\Fillable; use Illuminate\Database\Eloquent\Attributes\Guarded; use Illuminate\Database\Eloquent\Attributes\Hidden; use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Relations\MorphToMany; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; use Laravel\Passport\HasApiTokens; @@ -19,4 +20,9 @@ class User extends Authenticatable /** @use HasFactory */ use HasFactory, Notifiable, HasApiTokens; protected $guarded = ['id']; + + public function documents(): MorphToMany + { + return $this->morphToMany(Document::class, 'documentable'); + } } diff --git a/backend/database/factories/ExpertFactory.php b/backend/database/factories/ExpertFactory.php new file mode 100644 index 00000000..6daae3d7 --- /dev/null +++ b/backend/database/factories/ExpertFactory.php @@ -0,0 +1,24 @@ + + */ +class ExpertFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + // + ]; + } +} diff --git a/backend/database/migrations/0001_01_01_000000_create_users_table.php b/backend/database/migrations/0001_01_01_000000_create_users_table.php index faeaec9b..16d24730 100755 --- a/backend/database/migrations/0001_01_01_000000_create_users_table.php +++ b/backend/database/migrations/0001_01_01_000000_create_users_table.php @@ -29,7 +29,6 @@ return new class extends Migration $table->string('province_name')->nullable(); $table->foreignId('city_id')->nullable()->constrained('cities'); $table->string('city_name')->nullable(); - $table->timestamps(); }); diff --git a/backend/database/migrations/2026_05_12_074230_create_experts_table.php b/backend/database/migrations/2026_05_12_074230_create_experts_table.php new file mode 100644 index 00000000..63371886 --- /dev/null +++ b/backend/database/migrations/2026_05_12_074230_create_experts_table.php @@ -0,0 +1,39 @@ +id(); + $table->string('username'); + $table->string('first_name')->nullable(); + $table->string('last_name')->nullable(); + $table->string('email')->unique(); + $table->string('password'); + $table->string('national_id')->nullable(); + $table->string('phone_number')->nullable(); + $table->foreignId('province_id')->nullable()->constrained('provinces'); + $table->string('province_name')->nullable(); + $table->foreignId('city_id')->nullable()->constrained('cities'); + $table->string('city_name')->nullable(); + $table->tinyInteger('level')->default(1); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('experts'); + } +}; diff --git a/backend/database/seeders/ExpertSeeder.php b/backend/database/seeders/ExpertSeeder.php new file mode 100644 index 00000000..99da2770 --- /dev/null +++ b/backend/database/seeders/ExpertSeeder.php @@ -0,0 +1,17 @@ + Date: Tue, 12 May 2026 13:50:48 +0330 Subject: [PATCH 24/61] create new route for expert --- .gitignore | 3 ++- .../{Controller => Controllers}/AuthController.php | 2 +- .../Dashboard/ConfirmController.php | 2 +- .../ProfileController.php | 2 +- backend/bootstrap/app.php | 6 ++++++ backend/routes/expert.php | 12 ++++++++++++ backend/routes/web.php | 4 ++-- 7 files changed, 25 insertions(+), 6 deletions(-) rename backend/app/User/{Controller => Controllers}/AuthController.php (98%) rename backend/app/User/{Controller => Controllers}/Dashboard/ConfirmController.php (80%) rename backend/app/User/{Controller => Controllers}/ProfileController.php (97%) create mode 100755 backend/routes/expert.php diff --git a/.gitignore b/.gitignore index 3bf780b6..c31b3a14 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .idea -.env \ No newline at end of file +.env +front \ No newline at end of file diff --git a/backend/app/User/Controller/AuthController.php b/backend/app/User/Controllers/AuthController.php similarity index 98% rename from backend/app/User/Controller/AuthController.php rename to backend/app/User/Controllers/AuthController.php index 8fc61c9b..a967436c 100644 --- a/backend/app/User/Controller/AuthController.php +++ b/backend/app/User/Controllers/AuthController.php @@ -1,6 +1,6 @@ prefix('expert') + ->name('expert.') + ->group(base_path('routes/expert.php')); + }, ) ->withMiddleware(function (Middleware $middleware): void { $middleware->web()->preventRequestForgery(['*']); diff --git a/backend/routes/expert.php b/backend/routes/expert.php new file mode 100755 index 00000000..27f18ca6 --- /dev/null +++ b/backend/routes/expert.php @@ -0,0 +1,12 @@ +name('documents.') + ->controller(DocumentationController::class) + ->group(function () { + Route::post('/', 'index')->name('index'); + Route::post('confirm', 'confirm')->name('confirm'); + Route::post('reject', 'reject')->name('reject'); + }); diff --git a/backend/routes/web.php b/backend/routes/web.php index 2b0501ed..fc6836d2 100755 --- a/backend/routes/web.php +++ b/backend/routes/web.php @@ -1,7 +1,7 @@ Date: Tue, 12 May 2026 13:58:40 +0330 Subject: [PATCH 25/61] create base thing such as Roler permission Expert and user Management and their dependency for Admin --- .../ExpertManagementController.php | 97 ++++++++ .../PermissionManagementController.php | 78 +++++++ .../Controllers/RoleManagementController.php | 77 ++++++ .../Controllers/UserManagementController.php | 113 +++++++++ .../ExpertManagement/StoreRequest.php | 35 +++ .../ExpertManagement/UpdateRequest.php | 43 ++++ .../PermissionManagement/StoreRequest.php | 29 +++ .../PermissionManagement/UpdateRequest.php | 33 +++ .../Requests/RoleManagement/StoreRequest.php | 31 +++ .../Requests/RoleManagement/UpdateRequest.php | 35 +++ .../Requests/UserManagement/StoreRequest.php | 43 ++++ .../Requests/UserManagement/UpdateRequest.php | 52 +++++ .../Resources/ExpertManagementResource.php | 39 ++++ .../Resources/UserManagementResource.php | 43 ++++ backend/app/Models/User.php | 1 - .../Dashboard/ConfirmController.php | 14 -- .../Dashboard/DocumentationController.php | 24 ++ backend/composer.json | 3 +- backend/composer.lock | 150 +++++++++++- backend/config/permission.php | 219 ++++++++++++++++++ ..._05_10_123913_create_permission_tables.php | 137 +++++++++++ 21 files changed, 1279 insertions(+), 17 deletions(-) create mode 100644 backend/app/Admin/Controllers/ExpertManagementController.php create mode 100644 backend/app/Admin/Controllers/PermissionManagementController.php create mode 100755 backend/app/Admin/Controllers/RoleManagementController.php create mode 100644 backend/app/Admin/Controllers/UserManagementController.php create mode 100644 backend/app/Admin/Requests/ExpertManagement/StoreRequest.php create mode 100644 backend/app/Admin/Requests/ExpertManagement/UpdateRequest.php create mode 100644 backend/app/Admin/Requests/PermissionManagement/StoreRequest.php create mode 100644 backend/app/Admin/Requests/PermissionManagement/UpdateRequest.php create mode 100644 backend/app/Admin/Requests/RoleManagement/StoreRequest.php create mode 100644 backend/app/Admin/Requests/RoleManagement/UpdateRequest.php create mode 100644 backend/app/Admin/Requests/UserManagement/StoreRequest.php create mode 100644 backend/app/Admin/Requests/UserManagement/UpdateRequest.php create mode 100644 backend/app/Admin/Resources/ExpertManagementResource.php create mode 100644 backend/app/Admin/Resources/UserManagementResource.php delete mode 100755 backend/app/User/Controller/Dashboard/ConfirmController.php create mode 100755 backend/app/User/Controller/Dashboard/DocumentationController.php create mode 100644 backend/config/permission.php create mode 100644 backend/database/migrations/2026_05_10_123913_create_permission_tables.php diff --git a/backend/app/Admin/Controllers/ExpertManagementController.php b/backend/app/Admin/Controllers/ExpertManagementController.php new file mode 100644 index 00000000..ef793960 --- /dev/null +++ b/backend/app/Admin/Controllers/ExpertManagementController.php @@ -0,0 +1,97 @@ +json($data); + } + + /** + * Store a newly created resource in storage. + */ + public function store(StoreRequest $request): JsonResponse + { + Expert::query()->create([ + 'username' => $request->username, + 'first_name' => $request->first_name, + 'last_name' => $request->last_name, + 'national_id' => $request->national_id, + 'password' => Hash::make($request->password), + 'email' => $request->email, + 'phone_number' => $request->phone_number, + ]); + + return $this->successResponse(); + } + + /** + * Display the specified resource. + */ + public function show(Expert $expert): JsonResponse + { + return $this->successResponse( + new ExpertManagementResource($expert) + ); + } + + /** + * Update the specified resource in storage. + */ + public function update(UpdateRequest $request, Expert $expert): JsonResponse + { + $expert->update([ + 'username' => $request->username, + 'first_name' => $request->first_name, + 'last_name' => $request->last_name, + 'national_id' => $request->national_id, + 'password' => Hash::make($request->password), + 'email' => $request->email, + 'phone_number' => $request->phone_number, + ]); + + return $this->successResponse(); + } + + /** + * Remove the specified resource from storage. + * @throws Throwable + */ + public function destroy(Expert $expert): JsonResponse + { + DB::transaction(function () use ($expert) { + $expert->roles()->detach(); + $expert->permissions()->detach(); + $expert->delete(); + }); + + return $this->successResponse(); + } +} diff --git a/backend/app/Admin/Controllers/PermissionManagementController.php b/backend/app/Admin/Controllers/PermissionManagementController.php new file mode 100644 index 00000000..10462a70 --- /dev/null +++ b/backend/app/Admin/Controllers/PermissionManagementController.php @@ -0,0 +1,78 @@ +json( + DataTableFacade::run( + Permission::query(), + $request, + allowedFilters: ['*'], + allowedSortings: ['*'] + ) + ); + } + + /** + * Store a newly created resource in storage. + */ + public function store(StoreRequest $request): JsonResponse + { + Permission::create([ + 'name' => $request->name, + 'guard_name' => 'web', + ]); + + return $this->successResponse(); + } + + /** + * Display the specified resource. + */ + public function show(Permission $permission): JsonResponse + { + return $this->successResponse($permission); + } + + /** + * Update the specified resource in storage. + */ + public function update(UpdateRequest $request, Permission $permission): JsonResponse + { + $permission->update([ + 'name' => $request->name, + ]); + + return $this->successResponse(); + } + + /** + * Remove the specified resource from storage. + */ + public function destroy(Permission $permission): JsonResponse + { + $permission->delete(); + + return $this->successResponse(); + } +} diff --git a/backend/app/Admin/Controllers/RoleManagementController.php b/backend/app/Admin/Controllers/RoleManagementController.php new file mode 100755 index 00000000..0a64039c --- /dev/null +++ b/backend/app/Admin/Controllers/RoleManagementController.php @@ -0,0 +1,77 @@ +json($data); + } + + /** + * @throws Throwable + */ + public function store(StoreRequest $request): JsonResponse + { + DB::transaction(function () use ($request) { + $role = Role::create([ + 'name' => $request->name, + 'guard_name' => 'web', + ]); + + $role->givePermissionTo($request->permissions); + }); + + return $this->successResponse(); + } + + /** + * @throws Throwable + */ + public function update(UpdateRequest $request, Role $role): JsonResponse + { + DB::transaction(function () use ($request, $role) { + $role->update([ + 'name' => $request->name, + 'guard_name' => 'web', + ]); + + $role->syncPermissions($request->permissions); + }); + + return $this->successResponse(); + } + + public function show(Role $role): JsonResponse + { + return $this->successResponse($role); + } + + public function destroy(Role $role): JsonResponse + { + $role->delete(); + return $this->successResponse(); + } +} diff --git a/backend/app/Admin/Controllers/UserManagementController.php b/backend/app/Admin/Controllers/UserManagementController.php new file mode 100644 index 00000000..94adf110 --- /dev/null +++ b/backend/app/Admin/Controllers/UserManagementController.php @@ -0,0 +1,113 @@ +json($data); + } + + /** + * Store a newly created resource in storage. + */ + public function store(StoreRequest $request): JsonResponse + { + User::query()->create([ + 'username' => $request->username, + 'first_name' => $request->first_name, + 'last_name' => $request->last_name, + 'national_id' => $request->national_id, + 'password' => Hash::make($request->password), + 'email' => $request->email, + 'birthday' => $request->birthday, + 'phone_number' => $request->phone_number, + 'postal_code' => $request->postal_code, + 'address' => $request->address, + 'province_id' => $request->province_id, + 'province_name' => Province::query()->find($request->province_id)->name, + 'city_id' => $request->city_id, + 'city_name' => City::query()->find($request->city_id)->name, + 'key_status' => $request->key_status, + 'level' => $request->level, + 'tax_id' => $request->tax_id, + ]); + + return $this->successResponse(); + } + + + /** + * Display the specified resource. + */ + public function show(User $user): JsonResponse + { + return $this->successResponse( + new UserManagementResource($user) + ); + } + + /** + * Update the specified resource in storage. + */ + public function update(UpdateRequest $request, User $user): JsonResponse + { + $user->update([ + 'first_name' => $request->first_name, + 'username' => $request->username, + 'last_name' => $request->last_name, + 'national_id' => $request->national_id, + 'password' => Hash::make($request->password), + 'email' => $request->email, + 'birthday' => $request->birthday, + 'phone_number' => $request->phone_number, + 'postal_code' => $request->postal_code, + 'address' => $request->address, + 'province_id' => $request->province_id, + 'province_name' => Province::query()->find($request->province_id)->name, + 'city_id' => $request->city_id, + 'city_name' => City::query()->find($request->city_id)->name, + 'key_status' => $request->key_status, + 'level' => $request->level, + 'tax_id' => $request->tax_id, + ]); + + return $this->successResponse(); + } + + /** + * Remove the specified resource from storage. + * @throws Throwable + */ + public function destroy(User $user): JsonResponse + { + $user->delete(); + + return $this->successResponse(); + } +} diff --git a/backend/app/Admin/Requests/ExpertManagement/StoreRequest.php b/backend/app/Admin/Requests/ExpertManagement/StoreRequest.php new file mode 100644 index 00000000..4018cf9f --- /dev/null +++ b/backend/app/Admin/Requests/ExpertManagement/StoreRequest.php @@ -0,0 +1,35 @@ + + */ + public function rules(): array + { + return [ + 'username' => 'required|string|max:255|unique:users,username', + 'first_name' => 'required|string|max:255', + 'last_name' => 'required|string|max:255', + 'national_id' => 'required|string|max:20|unique:users,national_id', + 'password' => 'required|string|min:8|confirmed', + 'email' => 'required|email|max:255|unique:users,email', + 'phone_number' => 'required|string|max:20|unique:users,phone_number', + ]; + } +} diff --git a/backend/app/Admin/Requests/ExpertManagement/UpdateRequest.php b/backend/app/Admin/Requests/ExpertManagement/UpdateRequest.php new file mode 100644 index 00000000..732d7fe9 --- /dev/null +++ b/backend/app/Admin/Requests/ExpertManagement/UpdateRequest.php @@ -0,0 +1,43 @@ + + */ + public function rules(): array + { + return [ + 'username' => ['string', 'max:255', + Rule::unique('users', 'username')->ignore($this->user->id),], + 'first_name' => 'string|max:255', + 'last_name' => 'string|max:255', + 'national_id' => ['string', 'max:20', + Rule::unique('users', 'national_id')->ignore($this->user->id),], + 'password' => 'nullable|string|min:8|confirmed', + 'email' => ['email', 'max:255', + Rule::unique('users', 'email')->ignore($this->user->id),], + 'phone_number' => ['string', 'max:20', + Rule::unique('users', 'phone_number')->ignore($this->user->id),], + ]; + } +} diff --git a/backend/app/Admin/Requests/PermissionManagement/StoreRequest.php b/backend/app/Admin/Requests/PermissionManagement/StoreRequest.php new file mode 100644 index 00000000..7f91eda7 --- /dev/null +++ b/backend/app/Admin/Requests/PermissionManagement/StoreRequest.php @@ -0,0 +1,29 @@ + + */ + public function rules(): array + { + return [ + 'name' => 'required|string|unique:permissions,name', + ]; + } +} diff --git a/backend/app/Admin/Requests/PermissionManagement/UpdateRequest.php b/backend/app/Admin/Requests/PermissionManagement/UpdateRequest.php new file mode 100644 index 00000000..529ec458 --- /dev/null +++ b/backend/app/Admin/Requests/PermissionManagement/UpdateRequest.php @@ -0,0 +1,33 @@ + + */ + public function rules(): array + { + return [ + 'name' => ['required', 'string', 'max:255', Rule::unique('permission', 'name')->ignore($this->permission->id)], + ]; + } +} diff --git a/backend/app/Admin/Requests/RoleManagement/StoreRequest.php b/backend/app/Admin/Requests/RoleManagement/StoreRequest.php new file mode 100644 index 00000000..b30125a9 --- /dev/null +++ b/backend/app/Admin/Requests/RoleManagement/StoreRequest.php @@ -0,0 +1,31 @@ + + */ + public function rules(): array + { + return [ + 'name' => 'required|string|unique:roles,name', + 'permissions' => 'required|array', + 'permissions.*.name' => 'required|string|exists:permissions,name', + ]; + } +} diff --git a/backend/app/Admin/Requests/RoleManagement/UpdateRequest.php b/backend/app/Admin/Requests/RoleManagement/UpdateRequest.php new file mode 100644 index 00000000..4dd1cbf0 --- /dev/null +++ b/backend/app/Admin/Requests/RoleManagement/UpdateRequest.php @@ -0,0 +1,35 @@ + + */ + public function rules(): array + { + return [ + 'name' => ['required', 'string', 'max:255', Rule::unique('roles', 'name')->ignore($this->role->id)], + 'permissions' => 'required|array', + 'permissions.*.name' => 'required|string|exists:permissions,name', + ]; + } +} diff --git a/backend/app/Admin/Requests/UserManagement/StoreRequest.php b/backend/app/Admin/Requests/UserManagement/StoreRequest.php new file mode 100644 index 00000000..d0e2a741 --- /dev/null +++ b/backend/app/Admin/Requests/UserManagement/StoreRequest.php @@ -0,0 +1,43 @@ + + */ + public function rules(): array + { + return [ + 'username' => 'required|string|max:255|unique:users,username', + 'first_name' => 'required|string|max:255', + 'last_name' => 'required|string|max:255', + 'national_id' => 'required|string|max:20|unique:users,national_id', + 'password' => 'required|string|min:6|confirmed', + 'email' => 'required|email|max:255|unique:users,email', + 'birthday' => 'nullable|date', + 'phone_number' => 'required|string|max:20|unique:users,phone_number', + 'postal_code' => 'nullable|string|max:20', + 'address' => 'nullable|string|max:1000', + 'province_id' => 'required|integer|exists:provinces,id', + 'city_id' => 'required|integer|exists:cities,id', + 'key_status' => 'nullable|string|max:255', + 'level' => 'nullable|string|max:255', + 'tax_id' => 'nullable|string|max:50', + ]; + } +} diff --git a/backend/app/Admin/Requests/UserManagement/UpdateRequest.php b/backend/app/Admin/Requests/UserManagement/UpdateRequest.php new file mode 100644 index 00000000..2f4774cd --- /dev/null +++ b/backend/app/Admin/Requests/UserManagement/UpdateRequest.php @@ -0,0 +1,52 @@ + + */ + public function rules(): array + { + + return [ + 'username' => ['string', 'max:255', + Rule::unique('users', 'username')->ignore($this->user->id),], + 'first_name' => 'string|max:255', + 'last_name' => 'string|max:255', + 'national_id' => ['string', 'max:20', + Rule::unique('users', 'national_id')->ignore($this->user->id),], + 'password' => 'nullable|string|min:8|confirmed', + 'email' => ['email', 'max:255', + Rule::unique('users', 'email')->ignore($this->user->id),], + 'birthday' => 'nullable|date', + 'phone_number' => ['string', 'max:20', + Rule::unique('users', 'phone_number')->ignore($this->user->id),], + 'postal_code' => 'nullable|string|max:20', + 'address' => 'nullable|string|max:1000', + 'province_id' => 'integer|exists:provinces,id', + 'city_id' => 'integer|exists:cities,id', + 'key_status' => 'nullable|string|max:255', + 'level' => 'nullable|string|max:255', + 'tax_id' => 'nullable|string|max:50', + ]; + } +} diff --git a/backend/app/Admin/Resources/ExpertManagementResource.php b/backend/app/Admin/Resources/ExpertManagementResource.php new file mode 100644 index 00000000..7cdb154b --- /dev/null +++ b/backend/app/Admin/Resources/ExpertManagementResource.php @@ -0,0 +1,39 @@ + + */ + public function toArray(Request $request): array + { + return [ + 'id' => $this->id, + 'username' => $this->username, + 'first_name' => $this->first_name, + 'last_name' => $this->last_name, + 'national_id' => $this->national_id, + 'email' => $this->email, + 'phone_number' => $this->phone_number, + 'roles' => $this->roles() + ->pluck('name') + ->values(), + + 'permissions' => $this->getAllPermissions() + ->pluck('name') + ->values(), + ]; + } +} + diff --git a/backend/app/Admin/Resources/UserManagementResource.php b/backend/app/Admin/Resources/UserManagementResource.php new file mode 100644 index 00000000..0cb7d05e --- /dev/null +++ b/backend/app/Admin/Resources/UserManagementResource.php @@ -0,0 +1,43 @@ + + */ + public function toArray(Request $request): array + { + return [ + 'id' => $this->id, + 'username' => $this->username, + 'first_name' => $this->first_name, + 'last_name' => $this->last_name, + 'national_id' => $this->national_id, + 'email' => $this->email, + 'phone_number' => $this->phone_number, + 'postal_code' => $this->postal_code, + 'address' => $this->address, + 'province_name' => $this->province_name, + 'city_name' => $this->city_name, + 'roles' => $this->roles() + ->pluck('name') + ->values(), + + 'permissions' => $this->getAllPermissions() + ->pluck('name') + ->values(), + ]; + } +} + diff --git a/backend/app/Models/User.php b/backend/app/Models/User.php index 9f73d3cc..989ac3a2 100755 --- a/backend/app/Models/User.php +++ b/backend/app/Models/User.php @@ -4,7 +4,6 @@ namespace App\Models; // use Illuminate\Contracts\Auth\MustVerifyEmail; use Database\Factories\UserFactory; -use Illuminate\Database\Eloquent\Attributes\Fillable; use Illuminate\Database\Eloquent\Attributes\Guarded; use Illuminate\Database\Eloquent\Attributes\Hidden; use Illuminate\Database\Eloquent\Factories\HasFactory; diff --git a/backend/app/User/Controller/Dashboard/ConfirmController.php b/backend/app/User/Controller/Dashboard/ConfirmController.php deleted file mode 100755 index b8e918e5..00000000 --- a/backend/app/User/Controller/Dashboard/ConfirmController.php +++ /dev/null @@ -1,14 +0,0 @@ - [ + + /* + * When using the "HasPermissions" trait from this package, we need to know which + * Eloquent model should be used to retrieve your permissions. Of course, it + * is often just the "Permission" model but you may use whatever you like. + * + * The model you want to use as a Permission model needs to implement the + * `Spatie\Permission\Contracts\Permission` contract. + */ + + 'permission' => Permission::class, + + /* + * When using the "HasRoles" trait from this package, we need to know which + * Eloquent model should be used to retrieve your roles. Of course, it + * is often just the "Role" model but you may use whatever you like. + * + * The model you want to use as a Role model needs to implement the + * `Spatie\Permission\Contracts\Role` contract. + */ + + 'role' => Role::class, + + /* + * When using the "Teams" feature from this package, we need to know which + * Eloquent model should be used to retrieve your teams. Of course, it + * is often just the "Team" model but you may use whatever you like. + */ + 'team' => null, + + /* + * When using the "HasModels" trait and passing raw IDs to syncModels, + * attachModels, or detachModels, this model class will be used to + * resolve those IDs. If null, defaults to the guard's model. + */ + 'default_model' => null, + ], + + 'table_names' => [ + + /* + * When using the "HasRoles" trait from this package, we need to know which + * table should be used to retrieve your roles. We have chosen a basic + * default value but you may easily change it to any table you like. + */ + + 'roles' => 'roles', + + /* + * When using the "HasPermissions" trait from this package, we need to know which + * table should be used to retrieve your permissions. We have chosen a basic + * default value but you may easily change it to any table you like. + */ + + 'permissions' => 'permissions', + + /* + * When using the "HasPermissions" trait from this package, we need to know which + * table should be used to retrieve your models permissions. We have chosen a + * basic default value but you may easily change it to any table you like. + */ + + 'model_has_permissions' => 'model_has_permissions', + + /* + * When using the "HasRoles" trait from this package, we need to know which + * table should be used to retrieve your models roles. We have chosen a + * basic default value but you may easily change it to any table you like. + */ + + 'model_has_roles' => 'model_has_roles', + + /* + * When using the "HasRoles" trait from this package, we need to know which + * table should be used to retrieve your roles permissions. We have chosen a + * basic default value but you may easily change it to any table you like. + */ + + 'role_has_permissions' => 'role_has_permissions', + ], + + 'column_names' => [ + /* + * Change this if you want to name the related pivots other than defaults + */ + 'role_pivot_key' => null, // default 'role_id', + 'permission_pivot_key' => null, // default 'permission_id', + + /* + * Change this if you want to name the related model primary key other than + * `model_id`. + * + * For example, this would be nice if your primary keys are all UUIDs. In + * that case, name this `model_uuid`. + */ + + 'model_morph_key' => 'model_id', + + /* + * Change this if you want to use the teams feature and your related model's + * foreign key is other than `team_id`. + */ + + 'team_foreign_key' => 'team_id', + ], + + /* + * When set to true, the method for checking permissions will be registered on the gate. + * Set this to false if you want to implement custom logic for checking permissions. + */ + + 'register_permission_check_method' => true, + + /* + * When set to true, Laravel\Octane\Events\OperationTerminated event listener will be registered + * this will refresh permissions on every TickTerminated, TaskTerminated and RequestTerminated + * NOTE: This should not be needed in most cases, but an Octane/Vapor combination benefited from it. + */ + 'register_octane_reset_listener' => false, + + /* + * Events will fire when a role or permission is assigned/unassigned: + * \Spatie\Permission\Events\RoleAttachedEvent + * \Spatie\Permission\Events\RoleDetachedEvent + * \Spatie\Permission\Events\PermissionAttachedEvent + * \Spatie\Permission\Events\PermissionDetachedEvent + * + * To enable, set to true, and then create listeners to watch these events. + */ + 'events_enabled' => false, + + /* + * Teams Feature. + * When set to true the package implements teams using the 'team_foreign_key'. + * If you want the migrations to register the 'team_foreign_key', you must + * set this to true before doing the migration. + * If you already did the migration then you must make a new migration to also + * add 'team_foreign_key' to 'roles', 'model_has_roles', and 'model_has_permissions' + * (view the latest version of this package's migration file) + */ + + 'teams' => false, + + /* + * The class to use to resolve the permissions team id + */ + 'team_resolver' => DefaultTeamResolver::class, + + /* + * Passport Client Credentials Grant + * When set to true the package will use Passports Client to check permissions + */ + + 'use_passport_client_credentials' => false, + + /* + * When set to true, the required permission names are added to exception messages. + * This could be considered an information leak in some contexts, so the default + * setting is false here for optimum safety. + */ + + 'display_permission_in_exception' => false, + + /* + * When set to true, the required role names are added to exception messages. + * This could be considered an information leak in some contexts, so the default + * setting is false here for optimum safety. + */ + + 'display_role_in_exception' => false, + + /* + * By default wildcard permission lookups are disabled. + * See documentation to understand supported syntax. + */ + + 'enable_wildcard_permission' => false, + + /* + * The class to use for interpreting wildcard permissions. + * If you need to modify delimiters, override the class and specify its name here. + */ + // 'wildcard_permission' => Spatie\Permission\WildcardPermission::class, + + /* Cache-specific settings */ + + 'cache' => [ + + /* + * By default all permissions are cached for 24 hours to speed up performance. + * When permissions or roles are updated the cache is flushed automatically. + */ + + 'expiration_time' => DateInterval::createFromDateString('24 hours'), + + /* + * The cache key used to store all permissions. + */ + + 'key' => 'spatie.permission.cache', + + /* + * You may optionally indicate a specific cache driver to use for permission and + * role caching using any of the `store` drivers listed in the cache.php config + * file. Using 'default' here means to use the `default` set in cache.php. + */ + + 'store' => 'default', + ], +]; diff --git a/backend/database/migrations/2026_05_10_123913_create_permission_tables.php b/backend/database/migrations/2026_05_10_123913_create_permission_tables.php new file mode 100644 index 00000000..89862751 --- /dev/null +++ b/backend/database/migrations/2026_05_10_123913_create_permission_tables.php @@ -0,0 +1,137 @@ +id(); // permission id + $table->string('name'); + $table->string('guard_name'); + $table->timestamps(); + + $table->unique(['name', 'guard_name']); + }); + + /** + * See `docs/prerequisites.md` for suggested lengths on 'name' and 'guard_name' if "1071 Specified key was too long" errors are encountered. + */ + Schema::create($tableNames['roles'], static function (Blueprint $table) use ($teams, $columnNames) { + $table->id(); // role id + if ($teams || config('permission.testing')) { // permission.testing is a fix for sqlite testing + $table->unsignedBigInteger($columnNames['team_foreign_key'])->nullable(); + $table->index($columnNames['team_foreign_key'], 'roles_team_foreign_key_index'); + } + $table->string('name'); + $table->string('guard_name'); + $table->timestamps(); + if ($teams || config('permission.testing')) { + $table->unique([$columnNames['team_foreign_key'], 'name', 'guard_name']); + } else { + $table->unique(['name', 'guard_name']); + } + }); + + Schema::create($tableNames['model_has_permissions'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotPermission, $teams) { + $table->unsignedBigInteger($pivotPermission); + + $table->string('model_type'); + $table->unsignedBigInteger($columnNames['model_morph_key']); + $table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_model_id_model_type_index'); + + $table->foreign($pivotPermission) + ->references('id') // permission id + ->on($tableNames['permissions']) + ->cascadeOnDelete(); + if ($teams) { + $table->unsignedBigInteger($columnNames['team_foreign_key']); + $table->index($columnNames['team_foreign_key'], 'model_has_permissions_team_foreign_key_index'); + + $table->primary([$columnNames['team_foreign_key'], $pivotPermission, $columnNames['model_morph_key'], 'model_type'], + 'model_has_permissions_permission_model_type_primary'); + } else { + $table->primary([$pivotPermission, $columnNames['model_morph_key'], 'model_type'], + 'model_has_permissions_permission_model_type_primary'); + } + }); + + Schema::create($tableNames['model_has_roles'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotRole, $teams) { + $table->unsignedBigInteger($pivotRole); + + $table->string('model_type'); + $table->unsignedBigInteger($columnNames['model_morph_key']); + $table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_roles_model_id_model_type_index'); + + $table->foreign($pivotRole) + ->references('id') // role id + ->on($tableNames['roles']) + ->cascadeOnDelete(); + if ($teams) { + $table->unsignedBigInteger($columnNames['team_foreign_key']); + $table->index($columnNames['team_foreign_key'], 'model_has_roles_team_foreign_key_index'); + + $table->primary([$columnNames['team_foreign_key'], $pivotRole, $columnNames['model_morph_key'], 'model_type'], + 'model_has_roles_role_model_type_primary'); + } else { + $table->primary([$pivotRole, $columnNames['model_morph_key'], 'model_type'], + 'model_has_roles_role_model_type_primary'); + } + }); + + Schema::create($tableNames['role_has_permissions'], static function (Blueprint $table) use ($tableNames, $pivotRole, $pivotPermission) { + $table->unsignedBigInteger($pivotPermission); + $table->unsignedBigInteger($pivotRole); + + $table->foreign($pivotPermission) + ->references('id') // permission id + ->on($tableNames['permissions']) + ->cascadeOnDelete(); + + $table->foreign($pivotRole) + ->references('id') // role id + ->on($tableNames['roles']) + ->cascadeOnDelete(); + + $table->primary([$pivotPermission, $pivotRole], 'role_has_permissions_permission_id_role_id_primary'); + }); + + app('cache') + ->store(config('permission.cache.store') != 'default' ? config('permission.cache.store') : null) + ->forget(config('permission.cache.key')); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + $tableNames = config('permission.table_names'); + + throw_if(empty($tableNames), 'Error: config/permission.php not found and defaults could not be merged. Please publish the package configuration before proceeding, or drop the tables manually.'); + + Schema::dropIfExists($tableNames['role_has_permissions']); + Schema::dropIfExists($tableNames['model_has_roles']); + Schema::dropIfExists($tableNames['model_has_permissions']); + Schema::dropIfExists($tableNames['roles']); + Schema::dropIfExists($tableNames['permissions']); + } +}; -- 2.49.1 From f29510d76e96b11abd876143e99a6b33df4c1694 Mon Sep 17 00:00:00 2001 From: amirghasempoor Date: Tue, 12 May 2026 14:32:18 +0330 Subject: [PATCH 26/61] add level to users table --- .../Admin/Controllers/DocumentationController.php | 13 +++++++++---- .../0001_01_01_000000_create_users_table.php | 1 + .../2026_05_12_074230_create_experts_table.php | 1 - backend/routes/expert.php | 4 +++- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/backend/app/Admin/Controllers/DocumentationController.php b/backend/app/Admin/Controllers/DocumentationController.php index 357c7b21..d1de7b04 100644 --- a/backend/app/Admin/Controllers/DocumentationController.php +++ b/backend/app/Admin/Controllers/DocumentationController.php @@ -16,18 +16,23 @@ class DocumentationController extends Controller public function index(Request $request): array { return DataTableFacade::run( - User::query()->with('documents'), + User::query(), $request, allowedFilters: ['*'], allowedSortings: ['*'], allowedSelects: [ - 'users.id', 'users.first_name', 'users.last_name', 'users.national_id', - 'users.phone_number', 'users.postal_code', 'users.city_id', 'users.province_id', - 'users.province_name', 'users.city_name', 'users.address', 'documents.title', 'documents.url' + 'id', 'first_name', 'last_name', 'national_id', + 'phone_number', 'postal_code', 'city_id', 'province_id', + 'province_name', 'city_name', 'address' ] ); } + public function files(User $user): JsonResponse + { + return $this->successResponse($user->load('documents')); + } + public function confirm(User $user): JsonResponse { $user->update([ diff --git a/backend/database/migrations/0001_01_01_000000_create_users_table.php b/backend/database/migrations/0001_01_01_000000_create_users_table.php index 16d24730..0ac11763 100755 --- a/backend/database/migrations/0001_01_01_000000_create_users_table.php +++ b/backend/database/migrations/0001_01_01_000000_create_users_table.php @@ -29,6 +29,7 @@ return new class extends Migration $table->string('province_name')->nullable(); $table->foreignId('city_id')->nullable()->constrained('cities'); $table->string('city_name')->nullable(); + $table->tinyInteger('authority_level')->default(0); $table->timestamps(); }); diff --git a/backend/database/migrations/2026_05_12_074230_create_experts_table.php b/backend/database/migrations/2026_05_12_074230_create_experts_table.php index 63371886..de3673dd 100644 --- a/backend/database/migrations/2026_05_12_074230_create_experts_table.php +++ b/backend/database/migrations/2026_05_12_074230_create_experts_table.php @@ -24,7 +24,6 @@ return new class extends Migration $table->string('province_name')->nullable(); $table->foreignId('city_id')->nullable()->constrained('cities'); $table->string('city_name')->nullable(); - $table->tinyInteger('level')->default(1); $table->timestamps(); }); } diff --git a/backend/routes/expert.php b/backend/routes/expert.php index 27f18ca6..efe53012 100755 --- a/backend/routes/expert.php +++ b/backend/routes/expert.php @@ -4,9 +4,11 @@ use App\Admin\Controllers\DocumentationController; Route::prefix('documents') ->name('documents.') + ->middleware('auth') ->controller(DocumentationController::class) ->group(function () { - Route::post('/', 'index')->name('index'); + Route::get('/', 'index')->name('index'); Route::post('confirm', 'confirm')->name('confirm'); Route::post('reject', 'reject')->name('reject'); + Route::get('files/{user}', 'files')->name('files'); }); -- 2.49.1 From 32c14bd6c49e56179edde8cbb255d6aa4bb820c7 Mon Sep 17 00:00:00 2001 From: faezehzafarbakhsh Date: Wed, 13 May 2026 08:56:02 +0330 Subject: [PATCH 27/61] save for get develop --- .../Controllers/UserManagementController.php | 22 +++++--- .../Requests/UserManagement/StoreRequest.php | 2 +- .../Requests/UserManagement/UpdateRequest.php | 2 +- backend/routes/web.php | 50 +++++++++++++++++++ 4 files changed, 67 insertions(+), 9 deletions(-) diff --git a/backend/app/Admin/Controllers/UserManagementController.php b/backend/app/Admin/Controllers/UserManagementController.php index 94adf110..81ad99ad 100644 --- a/backend/app/Admin/Controllers/UserManagementController.php +++ b/backend/app/Admin/Controllers/UserManagementController.php @@ -27,7 +27,9 @@ class UserManagementController extends Controller $request, allowedFilters: ['*'], allowedSortings: ['*'], - allowedSelects: ['id', 'username', 'province_id', 'city_id', 'first_name', 'last_name', 'mobile',], + allowedSelects: ['id', 'username','first_name', 'last_name','kyc_status','national_id', + 'phone_number','postal_code','province_id','province_name', 'city_id', 'city_name', + 'birth_date','tax_id', 'password', 'email', 'level', 'key_status'], ); return response()->json($data); @@ -38,6 +40,9 @@ class UserManagementController extends Controller */ public function store(StoreRequest $request): JsonResponse { + $province = Province::query()->find($request->province_id); + $city = City::query()->find($request->city_id); + User::query()->create([ 'username' => $request->username, 'first_name' => $request->first_name, @@ -45,14 +50,14 @@ class UserManagementController extends Controller 'national_id' => $request->national_id, 'password' => Hash::make($request->password), 'email' => $request->email, - 'birthday' => $request->birthday, + 'birth_date' => $request->birth_date, 'phone_number' => $request->phone_number, 'postal_code' => $request->postal_code, 'address' => $request->address, 'province_id' => $request->province_id, - 'province_name' => Province::query()->find($request->province_id)->name, + 'province_name' => $province->name, 'city_id' => $request->city_id, - 'city_name' => City::query()->find($request->city_id)->name, + 'city_name' => $city->name, 'key_status' => $request->key_status, 'level' => $request->level, 'tax_id' => $request->tax_id, @@ -77,6 +82,9 @@ class UserManagementController extends Controller */ public function update(UpdateRequest $request, User $user): JsonResponse { + $province = Province::query()->find($request->province_id); + $city = City::query()->find($request->city_id); + $user->update([ 'first_name' => $request->first_name, 'username' => $request->username, @@ -84,14 +92,14 @@ class UserManagementController extends Controller 'national_id' => $request->national_id, 'password' => Hash::make($request->password), 'email' => $request->email, - 'birthday' => $request->birthday, + 'birth_date' => $request->birth_date, 'phone_number' => $request->phone_number, 'postal_code' => $request->postal_code, 'address' => $request->address, 'province_id' => $request->province_id, - 'province_name' => Province::query()->find($request->province_id)->name, + 'province_name' => $province->name, 'city_id' => $request->city_id, - 'city_name' => City::query()->find($request->city_id)->name, + 'city_name' => $city->name, 'key_status' => $request->key_status, 'level' => $request->level, 'tax_id' => $request->tax_id, diff --git a/backend/app/Admin/Requests/UserManagement/StoreRequest.php b/backend/app/Admin/Requests/UserManagement/StoreRequest.php index d0e2a741..a3c9cf92 100644 --- a/backend/app/Admin/Requests/UserManagement/StoreRequest.php +++ b/backend/app/Admin/Requests/UserManagement/StoreRequest.php @@ -29,7 +29,7 @@ class StoreRequest extends FormRequest 'national_id' => 'required|string|max:20|unique:users,national_id', 'password' => 'required|string|min:6|confirmed', 'email' => 'required|email|max:255|unique:users,email', - 'birthday' => 'nullable|date', + 'birth_date' => 'nullable|date', 'phone_number' => 'required|string|max:20|unique:users,phone_number', 'postal_code' => 'nullable|string|max:20', 'address' => 'nullable|string|max:1000', diff --git a/backend/app/Admin/Requests/UserManagement/UpdateRequest.php b/backend/app/Admin/Requests/UserManagement/UpdateRequest.php index 2f4774cd..4d8c56b4 100644 --- a/backend/app/Admin/Requests/UserManagement/UpdateRequest.php +++ b/backend/app/Admin/Requests/UserManagement/UpdateRequest.php @@ -37,7 +37,7 @@ class UpdateRequest extends FormRequest 'password' => 'nullable|string|min:8|confirmed', 'email' => ['email', 'max:255', Rule::unique('users', 'email')->ignore($this->user->id),], - 'birthday' => 'nullable|date', + 'birth_date' => 'nullable|date', 'phone_number' => ['string', 'max:20', Rule::unique('users', 'phone_number')->ignore($this->user->id),], 'postal_code' => 'nullable|string|max:20', diff --git a/backend/routes/web.php b/backend/routes/web.php index fc6836d2..439b076a 100755 --- a/backend/routes/web.php +++ b/backend/routes/web.php @@ -1,5 +1,9 @@ name('edit'); Route::post('change_password', 'changePassword')->name('changePassword'); }); + +Route::prefix('admin') + ->name('Admin.') + ->group(function () { + Route::prefix('user_management') + ->name('user_management.') + ->controller(UserManagementController::class) + ->group(function () { + Route::get('/', 'index')->name('index'); + Route::post('/', 'store')->name('store'); + Route::get('/{id}', 'show')->name('show'); + Route::post('/{id}', 'update')->name('update'); + Route::post('destroy/{id}', 'destroy')->name('destroy'); + }); + Route::prefix('roles') + ->name('roles.') + ->controller(RoleManagementController::class) + ->group(function () { + Route::get('/', 'index')->name('index'); + Route::post('/', 'store')->name('store'); + Route::get('/{id}', 'show')->name('show'); + Route::post('/{id}', 'update')->name('update'); + Route::post('destroy/{id}', 'destroy')->name('destroy'); + }); + Route::prefix('permissions') + ->name('permissions.') + ->controller(PermissionManagementController::class) + ->group(function () { + Route::get('/', 'index')->name('index'); + Route::post('/', 'store')->name('store'); + Route::get('/{id}', 'show')->name('show'); + Route::post('/{id}', 'update')->name('update'); + Route::post('destroy/{id}', 'destroy')->name('destroy'); + }); + Route::prefix('expert') + ->name('expert.') + ->controller(ExpertManagementController::class) + ->group(function () { + Route::get('/', 'index')->name('index'); + Route::post('/', 'store')->name('store'); + Route::get('/{id}', 'show')->name('show'); + Route::post('/{id}', 'update')->name('update'); + Route::post('destroy/{id}', 'destroy')->name('destroy'); + }); + }); + -- 2.49.1 From ae8ca057fd6e7b0c34c60701faa3d0de98371273 Mon Sep 17 00:00:00 2001 From: faezehzafarbakhsh Date: Wed, 13 May 2026 09:27:03 +0330 Subject: [PATCH 28/61] change some item in controller and request --- .../ExpertManagementController.php | 19 +++++++++++++++++++ .../Controllers/UserManagementController.php | 6 ------ .../ExpertManagement/StoreRequest.php | 4 +++- .../ExpertManagement/UpdateRequest.php | 2 ++ .../Requests/UserManagement/StoreRequest.php | 3 --- .../Requests/UserManagement/UpdateRequest.php | 3 --- .../Resources/ExpertManagementResource.php | 2 ++ backend/app/Models/Expert.php | 4 ++++ 8 files changed, 30 insertions(+), 13 deletions(-) diff --git a/backend/app/Admin/Controllers/ExpertManagementController.php b/backend/app/Admin/Controllers/ExpertManagementController.php index ef793960..6178646e 100644 --- a/backend/app/Admin/Controllers/ExpertManagementController.php +++ b/backend/app/Admin/Controllers/ExpertManagementController.php @@ -7,6 +7,9 @@ use App\Admin\Requests\ExpertManagement\UpdateRequest; use App\Admin\Resources\ExpertManagementResource; use App\Facades\DataTable\DataTableFacade; use App\Http\Controllers\Controller; +use App\Models\City; +use App\Models\Expert; +use App\Models\Province; use App\Traits\ApiResponse; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; @@ -39,6 +42,9 @@ class ExpertManagementController extends Controller */ public function store(StoreRequest $request): JsonResponse { + $province = Province::query()->find($request->province_id); + $city = City::query()->find($request->city_id); + Expert::query()->create([ 'username' => $request->username, 'first_name' => $request->first_name, @@ -47,6 +53,11 @@ class ExpertManagementController extends Controller 'password' => Hash::make($request->password), 'email' => $request->email, 'phone_number' => $request->phone_number, + 'province_id' => $request->province_id, + 'province_name' => $province->name, + 'city_id' => $request->city_id, + 'city_name' => $city->name, + 'level' => $request->level, ]); return $this->successResponse(); @@ -67,6 +78,9 @@ class ExpertManagementController extends Controller */ public function update(UpdateRequest $request, Expert $expert): JsonResponse { + $province = Province::query()->find($request->province_id); + $city = City::query()->find($request->city_id); + $expert->update([ 'username' => $request->username, 'first_name' => $request->first_name, @@ -75,6 +89,11 @@ class ExpertManagementController extends Controller 'password' => Hash::make($request->password), 'email' => $request->email, 'phone_number' => $request->phone_number, + 'province_id' => $request->province_id, + 'province_name' => $province->name, + 'city_id' => $request->city_id, + 'city_name' => $city->name, + 'level' => $request->level, ]); return $this->successResponse(); diff --git a/backend/app/Admin/Controllers/UserManagementController.php b/backend/app/Admin/Controllers/UserManagementController.php index 81ad99ad..fc89237a 100644 --- a/backend/app/Admin/Controllers/UserManagementController.php +++ b/backend/app/Admin/Controllers/UserManagementController.php @@ -58,9 +58,6 @@ class UserManagementController extends Controller 'province_name' => $province->name, 'city_id' => $request->city_id, 'city_name' => $city->name, - 'key_status' => $request->key_status, - 'level' => $request->level, - 'tax_id' => $request->tax_id, ]); return $this->successResponse(); @@ -100,9 +97,6 @@ class UserManagementController extends Controller 'province_name' => $province->name, 'city_id' => $request->city_id, 'city_name' => $city->name, - 'key_status' => $request->key_status, - 'level' => $request->level, - 'tax_id' => $request->tax_id, ]); return $this->successResponse(); diff --git a/backend/app/Admin/Requests/ExpertManagement/StoreRequest.php b/backend/app/Admin/Requests/ExpertManagement/StoreRequest.php index 4018cf9f..16a1612c 100644 --- a/backend/app/Admin/Requests/ExpertManagement/StoreRequest.php +++ b/backend/app/Admin/Requests/ExpertManagement/StoreRequest.php @@ -30,6 +30,8 @@ class StoreRequest extends FormRequest 'password' => 'required|string|min:8|confirmed', 'email' => 'required|email|max:255|unique:users,email', 'phone_number' => 'required|string|max:20|unique:users,phone_number', - ]; + 'province_id' => 'required|integer|exists:provinces,id', + 'city_id' => 'required|integer|exists:cities,id', + ]; } } diff --git a/backend/app/Admin/Requests/ExpertManagement/UpdateRequest.php b/backend/app/Admin/Requests/ExpertManagement/UpdateRequest.php index 732d7fe9..da3ed7ed 100644 --- a/backend/app/Admin/Requests/ExpertManagement/UpdateRequest.php +++ b/backend/app/Admin/Requests/ExpertManagement/UpdateRequest.php @@ -38,6 +38,8 @@ class UpdateRequest extends FormRequest Rule::unique('users', 'email')->ignore($this->user->id),], 'phone_number' => ['string', 'max:20', Rule::unique('users', 'phone_number')->ignore($this->user->id),], + 'province_id' => 'required|integer|exists:provinces,id', + 'city_id' => 'required|integer|exists:cities,id', ]; } } diff --git a/backend/app/Admin/Requests/UserManagement/StoreRequest.php b/backend/app/Admin/Requests/UserManagement/StoreRequest.php index a3c9cf92..38208575 100644 --- a/backend/app/Admin/Requests/UserManagement/StoreRequest.php +++ b/backend/app/Admin/Requests/UserManagement/StoreRequest.php @@ -35,9 +35,6 @@ class StoreRequest extends FormRequest 'address' => 'nullable|string|max:1000', 'province_id' => 'required|integer|exists:provinces,id', 'city_id' => 'required|integer|exists:cities,id', - 'key_status' => 'nullable|string|max:255', - 'level' => 'nullable|string|max:255', - 'tax_id' => 'nullable|string|max:50', ]; } } diff --git a/backend/app/Admin/Requests/UserManagement/UpdateRequest.php b/backend/app/Admin/Requests/UserManagement/UpdateRequest.php index 4d8c56b4..2c9f363a 100644 --- a/backend/app/Admin/Requests/UserManagement/UpdateRequest.php +++ b/backend/app/Admin/Requests/UserManagement/UpdateRequest.php @@ -44,9 +44,6 @@ class UpdateRequest extends FormRequest 'address' => 'nullable|string|max:1000', 'province_id' => 'integer|exists:provinces,id', 'city_id' => 'integer|exists:cities,id', - 'key_status' => 'nullable|string|max:255', - 'level' => 'nullable|string|max:255', - 'tax_id' => 'nullable|string|max:50', ]; } } diff --git a/backend/app/Admin/Resources/ExpertManagementResource.php b/backend/app/Admin/Resources/ExpertManagementResource.php index 7cdb154b..788c0c93 100644 --- a/backend/app/Admin/Resources/ExpertManagementResource.php +++ b/backend/app/Admin/Resources/ExpertManagementResource.php @@ -26,6 +26,8 @@ class ExpertManagementResource extends JsonResource 'national_id' => $this->national_id, 'email' => $this->email, 'phone_number' => $this->phone_number, + 'province_name' => $this->province_name, + 'city_name' => $this->city_name, 'roles' => $this->roles() ->pluck('name') ->values(), diff --git a/backend/app/Models/Expert.php b/backend/app/Models/Expert.php index 214bb797..b55f8f55 100644 --- a/backend/app/Models/Expert.php +++ b/backend/app/Models/Expert.php @@ -6,6 +6,10 @@ use Database\Factories\ExpertFactory; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +/** + * @method roles() + * @method permissions() + */ class Expert extends Model { /** @use HasFactory */ -- 2.49.1 From 7b0507bdda77f16c7a564d37651d483d80b99e1b Mon Sep 17 00:00:00 2001 From: faezehzafarbakhsh Date: Wed, 13 May 2026 10:33:59 +0330 Subject: [PATCH 29/61] fix code --- .../ExpertManagementController.php | 2 - .../Controllers/RoleManagementController.php | 2 +- .../Controllers/UserManagementController.php | 8 ++-- .../ExpertManagement/StoreRequest.php | 8 ++-- .../ExpertManagement/UpdateRequest.php | 12 +++--- .../Requests/UserManagement/UpdateRequest.php | 2 +- .../Resources/UserManagementResource.php | 43 ------------------- 7 files changed, 15 insertions(+), 62 deletions(-) delete mode 100644 backend/app/Admin/Resources/UserManagementResource.php diff --git a/backend/app/Admin/Controllers/ExpertManagementController.php b/backend/app/Admin/Controllers/ExpertManagementController.php index 6178646e..6591a8a8 100644 --- a/backend/app/Admin/Controllers/ExpertManagementController.php +++ b/backend/app/Admin/Controllers/ExpertManagementController.php @@ -57,7 +57,6 @@ class ExpertManagementController extends Controller 'province_name' => $province->name, 'city_id' => $request->city_id, 'city_name' => $city->name, - 'level' => $request->level, ]); return $this->successResponse(); @@ -93,7 +92,6 @@ class ExpertManagementController extends Controller 'province_name' => $province->name, 'city_id' => $request->city_id, 'city_name' => $city->name, - 'level' => $request->level, ]); return $this->successResponse(); diff --git a/backend/app/Admin/Controllers/RoleManagementController.php b/backend/app/Admin/Controllers/RoleManagementController.php index 0a64039c..c1dc1004 100755 --- a/backend/app/Admin/Controllers/RoleManagementController.php +++ b/backend/app/Admin/Controllers/RoleManagementController.php @@ -24,7 +24,7 @@ class RoleManagementController extends Controller $request, allowedFilters: ['*'], allowedSortings: ['*'], - allowedSelects: ['id', 'name', 'description'] + allowedSelects: ['id', 'name'] ); return response()->json($data); diff --git a/backend/app/Admin/Controllers/UserManagementController.php b/backend/app/Admin/Controllers/UserManagementController.php index fc89237a..63e96da7 100644 --- a/backend/app/Admin/Controllers/UserManagementController.php +++ b/backend/app/Admin/Controllers/UserManagementController.php @@ -4,7 +4,6 @@ namespace App\Admin\Controllers; use App\Admin\Requests\UserManagement\StoreRequest; use App\Admin\Requests\UserManagement\UpdateRequest; -use App\Admin\Resources\UserManagementResource; use App\Facades\DataTable\DataTableFacade; use App\Http\Controllers\Controller; use App\Models\City; @@ -29,7 +28,7 @@ class UserManagementController extends Controller allowedSortings: ['*'], allowedSelects: ['id', 'username','first_name', 'last_name','kyc_status','national_id', 'phone_number','postal_code','province_id','province_name', 'city_id', 'city_name', - 'birth_date','tax_id', 'password', 'email', 'level', 'key_status'], + 'birth_date','tax_id', 'password', 'email'], ); return response()->json($data); @@ -69,9 +68,8 @@ class UserManagementController extends Controller */ public function show(User $user): JsonResponse { - return $this->successResponse( - new UserManagementResource($user) - ); + return $this->successResponse($user); + } /** diff --git a/backend/app/Admin/Requests/ExpertManagement/StoreRequest.php b/backend/app/Admin/Requests/ExpertManagement/StoreRequest.php index 16a1612c..3a866cfe 100644 --- a/backend/app/Admin/Requests/ExpertManagement/StoreRequest.php +++ b/backend/app/Admin/Requests/ExpertManagement/StoreRequest.php @@ -23,13 +23,13 @@ class StoreRequest extends FormRequest public function rules(): array { return [ - 'username' => 'required|string|max:255|unique:users,username', + 'username' => 'required|string|max:255|unique:experts,username', 'first_name' => 'required|string|max:255', 'last_name' => 'required|string|max:255', - 'national_id' => 'required|string|max:20|unique:users,national_id', + 'national_id' => 'required|string|max:20|unique:experts,national_id', 'password' => 'required|string|min:8|confirmed', - 'email' => 'required|email|max:255|unique:users,email', - 'phone_number' => 'required|string|max:20|unique:users,phone_number', + 'email' => 'required|email|max:255|unique:experts,email', + 'phone_number' => 'required|string|max:20|unique:experts,phone_number', 'province_id' => 'required|integer|exists:provinces,id', 'city_id' => 'required|integer|exists:cities,id', ]; diff --git a/backend/app/Admin/Requests/ExpertManagement/UpdateRequest.php b/backend/app/Admin/Requests/ExpertManagement/UpdateRequest.php index da3ed7ed..f496445b 100644 --- a/backend/app/Admin/Requests/ExpertManagement/UpdateRequest.php +++ b/backend/app/Admin/Requests/ExpertManagement/UpdateRequest.php @@ -7,7 +7,7 @@ use Illuminate\Foundation\Http\FormRequest; use Illuminate\Validation\Rule; /** - * @property mixed $user + * @property mixed $expert */ class UpdateRequest extends FormRequest { @@ -28,16 +28,16 @@ class UpdateRequest extends FormRequest { return [ 'username' => ['string', 'max:255', - Rule::unique('users', 'username')->ignore($this->user->id),], + Rule::unique('experts', 'username')->ignore($this->expert->id),], 'first_name' => 'string|max:255', 'last_name' => 'string|max:255', 'national_id' => ['string', 'max:20', - Rule::unique('users', 'national_id')->ignore($this->user->id),], - 'password' => 'nullable|string|min:8|confirmed', + Rule::unique('experts', 'national_id')->ignore($this->expert->id),], + 'password' => 'string|min:8|confirmed', 'email' => ['email', 'max:255', - Rule::unique('users', 'email')->ignore($this->user->id),], + Rule::unique('experts', 'email')->ignore($this->expert->id),], 'phone_number' => ['string', 'max:20', - Rule::unique('users', 'phone_number')->ignore($this->user->id),], + Rule::unique('experts', 'phone_number')->ignore($this->expert->id),], 'province_id' => 'required|integer|exists:provinces,id', 'city_id' => 'required|integer|exists:cities,id', ]; diff --git a/backend/app/Admin/Requests/UserManagement/UpdateRequest.php b/backend/app/Admin/Requests/UserManagement/UpdateRequest.php index 2c9f363a..99c7af70 100644 --- a/backend/app/Admin/Requests/UserManagement/UpdateRequest.php +++ b/backend/app/Admin/Requests/UserManagement/UpdateRequest.php @@ -34,7 +34,7 @@ class UpdateRequest extends FormRequest 'last_name' => 'string|max:255', 'national_id' => ['string', 'max:20', Rule::unique('users', 'national_id')->ignore($this->user->id),], - 'password' => 'nullable|string|min:8|confirmed', + 'password' => 'string|min:8|confirmed', 'email' => ['email', 'max:255', Rule::unique('users', 'email')->ignore($this->user->id),], 'birth_date' => 'nullable|date', diff --git a/backend/app/Admin/Resources/UserManagementResource.php b/backend/app/Admin/Resources/UserManagementResource.php deleted file mode 100644 index 0cb7d05e..00000000 --- a/backend/app/Admin/Resources/UserManagementResource.php +++ /dev/null @@ -1,43 +0,0 @@ - - */ - public function toArray(Request $request): array - { - return [ - 'id' => $this->id, - 'username' => $this->username, - 'first_name' => $this->first_name, - 'last_name' => $this->last_name, - 'national_id' => $this->national_id, - 'email' => $this->email, - 'phone_number' => $this->phone_number, - 'postal_code' => $this->postal_code, - 'address' => $this->address, - 'province_name' => $this->province_name, - 'city_name' => $this->city_name, - 'roles' => $this->roles() - ->pluck('name') - ->values(), - - 'permissions' => $this->getAllPermissions() - ->pluck('name') - ->values(), - ]; - } -} - -- 2.49.1 From e33aac7eed14c31298ae4a55d8c88f2cea50711c Mon Sep 17 00:00:00 2001 From: faezehzafarbakhsh Date: Wed, 13 May 2026 13:33:58 +0330 Subject: [PATCH 30/61] fix code --- backend/routes/web.php | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/backend/routes/web.php b/backend/routes/web.php index 439b076a..b092aaac 100755 --- a/backend/routes/web.php +++ b/backend/routes/web.php @@ -36,9 +36,9 @@ Route::prefix('admin') ->group(function () { Route::get('/', 'index')->name('index'); Route::post('/', 'store')->name('store'); - Route::get('/{id}', 'show')->name('show'); - Route::post('/{id}', 'update')->name('update'); - Route::post('destroy/{id}', 'destroy')->name('destroy'); + Route::get('/{user}', 'show')->name('show'); + Route::post('/{user}', 'update')->name('update'); + Route::post('destroy/{user}', 'destroy')->name('destroy'); }); Route::prefix('roles') ->name('roles.') @@ -46,9 +46,9 @@ Route::prefix('admin') ->group(function () { Route::get('/', 'index')->name('index'); Route::post('/', 'store')->name('store'); - Route::get('/{id}', 'show')->name('show'); - Route::post('/{id}', 'update')->name('update'); - Route::post('destroy/{id}', 'destroy')->name('destroy'); + Route::get('/{role}', 'show')->name('show'); + Route::post('/{role}', 'update')->name('update'); + Route::post('destroy/{role}', 'destroy')->name('destroy'); }); Route::prefix('permissions') ->name('permissions.') @@ -56,9 +56,9 @@ Route::prefix('admin') ->group(function () { Route::get('/', 'index')->name('index'); Route::post('/', 'store')->name('store'); - Route::get('/{id}', 'show')->name('show'); - Route::post('/{id}', 'update')->name('update'); - Route::post('destroy/{id}', 'destroy')->name('destroy'); + Route::get('/{permission}', 'show')->name('show'); + Route::post('/{permission}', 'update')->name('update'); + Route::post('destroy/{permission}', 'destroy')->name('destroy'); }); Route::prefix('expert') ->name('expert.') @@ -66,9 +66,9 @@ Route::prefix('admin') ->group(function () { Route::get('/', 'index')->name('index'); Route::post('/', 'store')->name('store'); - Route::get('/{id}', 'show')->name('show'); - Route::post('/{id}', 'update')->name('update'); - Route::post('destroy/{id}', 'destroy')->name('destroy'); + Route::get('/{expert}', 'show')->name('show'); + Route::post('/{expert}', 'update')->name('update'); + Route::post('destroy/{expert}', 'destroy')->name('destroy'); }); }); -- 2.49.1 From 1a2077a164ba773f0d6643026dbb9602ec7428e1 Mon Sep 17 00:00:00 2001 From: amirghasempoor Date: Wed, 13 May 2026 13:41:12 +0330 Subject: [PATCH 31/61] create expert login resources --- .../app/Admin/Controllers/AuthController.php | 24 +++++++------- .../Controllers/DocumentationController.php | 8 +++-- .../Admin/Controllers/ProfileController.php | 20 +++++++++++- .../app/Admin/Requests/Auth/LoginRequest.php | 30 ++++++++++++++++++ .../Admin/Requests/Auth/RegisterRequest.php | 31 +++++++++++++++++++ .../Requests/Documentation/RejectRequest.php | 29 +++++++++++++++++ .../Profile/ChangePasswordRequest.php | 30 ++++++++++++++++++ .../app/User/Controllers/AuthController.php | 14 +++------ ...05_13_054404_add_column_to_users_table.php | 28 +++++++++++++++++ backend/routes/expert.php | 12 ++++++- 10 files changed, 201 insertions(+), 25 deletions(-) create mode 100644 backend/app/Admin/Requests/Auth/LoginRequest.php create mode 100644 backend/app/Admin/Requests/Auth/RegisterRequest.php create mode 100644 backend/app/Admin/Requests/Documentation/RejectRequest.php create mode 100644 backend/app/Admin/Requests/Profile/ChangePasswordRequest.php create mode 100644 backend/database/migrations/2026_05_13_054404_add_column_to_users_table.php diff --git a/backend/app/Admin/Controllers/AuthController.php b/backend/app/Admin/Controllers/AuthController.php index 5c794ff5..4ea69b0a 100644 --- a/backend/app/Admin/Controllers/AuthController.php +++ b/backend/app/Admin/Controllers/AuthController.php @@ -2,26 +2,34 @@ namespace App\Admin\Controllers; +use App\Admin\Requests\Auth\LoginRequest; +use App\Admin\Requests\Auth\RegisterRequest; use App\Http\Controllers\Controller; +use App\Models\Expert; use App\Traits\ApiResponse; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Hash; class AuthController extends Controller { use ApiResponse; - public function register(Request $request): JsonResponse + public function register(RegisterRequest $request): JsonResponse { -// Expert::query()->create(); + Expert::query()->create([ + 'username' => $request->username, + 'email' => $request->email, + 'password' => Hash::make($request->password), + ]); $request->session()->regenerate(); return $this->successResponse(); } - public function login(Request $request): JsonResponse + public function login(LoginRequest $request): JsonResponse { $credentials = ['password' => $request->password]; @@ -35,13 +43,7 @@ class AuthController extends Controller { $request->session()->regenerate(); - $user = Auth::user(); - - return $this->successResponse([ - 'id' => $user->id, - 'username' => $user->username, - 'email' => $user->email, - ]); + return $this->successResponse(); } return $this->errorResponse(__('messages.incorrect_or_username_password')); @@ -49,7 +51,7 @@ class AuthController extends Controller public function logout(Request $request): JsonResponse { - Auth::logout(); + Auth::guard('expert')->logout(); $request->session()->invalidate(); diff --git a/backend/app/Admin/Controllers/DocumentationController.php b/backend/app/Admin/Controllers/DocumentationController.php index d1de7b04..54e374e5 100644 --- a/backend/app/Admin/Controllers/DocumentationController.php +++ b/backend/app/Admin/Controllers/DocumentationController.php @@ -2,6 +2,7 @@ namespace App\Admin\Controllers; +use App\Admin\Requests\Documentation\RejectRequest; use App\Facades\DataTable\DataTableFacade; use App\Http\Controllers\Controller; use App\Models\User; @@ -36,16 +37,17 @@ class DocumentationController extends Controller public function confirm(User $user): JsonResponse { $user->update([ - + 'authority_level' => 1 ]); return $this->successResponse(); } - public function reject(User $user): JsonResponse + public function reject(RejectRequest $request, User $user): JsonResponse { $user->update([ - + 'kyc_status' => 0, + 'kyc_reject_reason' => $request->kyc_reject_reason ]); return $this->successResponse(); diff --git a/backend/app/Admin/Controllers/ProfileController.php b/backend/app/Admin/Controllers/ProfileController.php index 39d81669..5b825966 100644 --- a/backend/app/Admin/Controllers/ProfileController.php +++ b/backend/app/Admin/Controllers/ProfileController.php @@ -2,11 +2,13 @@ namespace App\Admin\Controllers; +use App\Admin\Requests\Profile\ChangePasswordRequest; use App\Admin\Resources\ExpertResource; use App\Http\Controllers\Controller; use App\Traits\ApiResponse; use Illuminate\Http\JsonResponse; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Hash; class ProfileController extends Controller { @@ -14,6 +16,22 @@ class ProfileController extends Controller public function info(): JsonResponse { - return $this->successResponse(new ExpertResource(Auth::user())); + return $this->successResponse(new ExpertResource(Auth::guard('expert')->user())); + } + + public function changePassword(ChangePasswordRequest $request): JsonResponse + { + $expert = Auth::guard('expert')->user(); + + if (! Hash::check($request->current_password, $expert->password)) + { + return $this->errorResponse(__('messages.incorrect_current_password')); + } + + $expert->update([ + 'password' => Hash::make($request->new_password) + ]); + + return $this->successResponse(); } } diff --git a/backend/app/Admin/Requests/Auth/LoginRequest.php b/backend/app/Admin/Requests/Auth/LoginRequest.php new file mode 100644 index 00000000..16c27d74 --- /dev/null +++ b/backend/app/Admin/Requests/Auth/LoginRequest.php @@ -0,0 +1,30 @@ + + */ + public function rules(): array + { + return [ + 'login' => 'required|string', + 'password' => 'required|string', + ]; + } +} diff --git a/backend/app/Admin/Requests/Auth/RegisterRequest.php b/backend/app/Admin/Requests/Auth/RegisterRequest.php new file mode 100644 index 00000000..be5ffa9a --- /dev/null +++ b/backend/app/Admin/Requests/Auth/RegisterRequest.php @@ -0,0 +1,31 @@ + + */ + public function rules(): array + { + return [ + 'username' => 'required|unique:experts,username', + 'email' => 'required|email|unique:experts,email', + 'password' => 'required|string|regex:/^(?=.*[a-zA-Z])(?=.*\d).+$/|min:6|confirmed', + ]; + } +} diff --git a/backend/app/Admin/Requests/Documentation/RejectRequest.php b/backend/app/Admin/Requests/Documentation/RejectRequest.php new file mode 100644 index 00000000..0a65ebdf --- /dev/null +++ b/backend/app/Admin/Requests/Documentation/RejectRequest.php @@ -0,0 +1,29 @@ + + */ + public function rules(): array + { + return [ + 'kyc_reject_reason' => 'required|string', + ]; + } +} diff --git a/backend/app/Admin/Requests/Profile/ChangePasswordRequest.php b/backend/app/Admin/Requests/Profile/ChangePasswordRequest.php new file mode 100644 index 00000000..f4f0953e --- /dev/null +++ b/backend/app/Admin/Requests/Profile/ChangePasswordRequest.php @@ -0,0 +1,30 @@ + + */ + public function rules(): array + { + return [ + 'current_password' => 'required|string|regex:/^(?=.*[a-zA-Z])(?=.*\d).+$/|min:8', + 'new_password' => 'required|string|regex:/^(?=.*[a-zA-Z])(?=.*\d).+$/|min:8', + ]; + } +} diff --git a/backend/app/User/Controllers/AuthController.php b/backend/app/User/Controllers/AuthController.php index a967436c..433624cc 100644 --- a/backend/app/User/Controllers/AuthController.php +++ b/backend/app/User/Controllers/AuthController.php @@ -18,12 +18,14 @@ class AuthController extends Controller public function register(RegisterRequest $request): JsonResponse { - User::query()->create([ + $user = User::query()->create([ 'username' => $request->username, 'email' => $request->email, 'password' => Hash::make($request->password), ]); + Auth::guard('web')->login($user); + $request->session()->regenerate(); return $this->successResponse(); @@ -43,13 +45,7 @@ class AuthController extends Controller { $request->session()->regenerate(); - $user = Auth::user(); - - return $this->successResponse([ - 'id' => $user->id, - 'username' => $user->username, - 'email' => $user->email, - ]); + return $this->successResponse(); } return $this->errorResponse(__('messages.incorrect_or_username_password')); @@ -57,7 +53,7 @@ class AuthController extends Controller public function logout(Request $request): JsonResponse { - Auth::logout(); + Auth::guard('web')->logout(); $request->session()->invalidate(); diff --git a/backend/database/migrations/2026_05_13_054404_add_column_to_users_table.php b/backend/database/migrations/2026_05_13_054404_add_column_to_users_table.php new file mode 100644 index 00000000..dcc326de --- /dev/null +++ b/backend/database/migrations/2026_05_13_054404_add_column_to_users_table.php @@ -0,0 +1,28 @@ +string('kyc_reject_reason')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('kyc_reject_reason'); + }); + } +}; diff --git a/backend/routes/expert.php b/backend/routes/expert.php index efe53012..db005e64 100755 --- a/backend/routes/expert.php +++ b/backend/routes/expert.php @@ -1,10 +1,20 @@ name('auth.') + ->controller(AuthController::class) + ->group(function () { + Route::post('register', 'register')->name('register'); + Route::post('login', 'login')->name('login'); + Route::post('logout', 'logout')->name('logout'); +}); + Route::prefix('documents') ->name('documents.') - ->middleware('auth') + ->middleware('auth:expert') ->controller(DocumentationController::class) ->group(function () { Route::get('/', 'index')->name('index'); -- 2.49.1 From af23df5527528713919d6f710f5ee0fb12ccff51 Mon Sep 17 00:00:00 2001 From: faezehzafarbakhsh Date: Wed, 13 May 2026 13:45:58 +0330 Subject: [PATCH 32/61] fix routs --- backend/routes/web.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/routes/web.php b/backend/routes/web.php index b092aaac..b81845c1 100755 --- a/backend/routes/web.php +++ b/backend/routes/web.php @@ -38,7 +38,7 @@ Route::prefix('admin') Route::post('/', 'store')->name('store'); Route::get('/{user}', 'show')->name('show'); Route::post('/{user}', 'update')->name('update'); - Route::post('destroy/{user}', 'destroy')->name('destroy'); + Route::delete('/{user}', 'destroy')->name('destroy'); }); Route::prefix('roles') ->name('roles.') @@ -48,7 +48,7 @@ Route::prefix('admin') Route::post('/', 'store')->name('store'); Route::get('/{role}', 'show')->name('show'); Route::post('/{role}', 'update')->name('update'); - Route::post('destroy/{role}', 'destroy')->name('destroy'); + Route::delete('/{role}', 'destroy')->name('destroy'); }); Route::prefix('permissions') ->name('permissions.') @@ -58,7 +58,7 @@ Route::prefix('admin') Route::post('/', 'store')->name('store'); Route::get('/{permission}', 'show')->name('show'); Route::post('/{permission}', 'update')->name('update'); - Route::post('destroy/{permission}', 'destroy')->name('destroy'); + Route::delete('/{permission}', 'destroy')->name('destroy'); }); Route::prefix('expert') ->name('expert.') @@ -68,7 +68,7 @@ Route::prefix('admin') Route::post('/', 'store')->name('store'); Route::get('/{expert}', 'show')->name('show'); Route::post('/{expert}', 'update')->name('update'); - Route::post('destroy/{expert}', 'destroy')->name('destroy'); + Route::delete('/{expert}', 'destroy')->name('destroy'); }); }); -- 2.49.1 From 581e7af1fd6a4575353d2b01c23f391238bfabdb Mon Sep 17 00:00:00 2001 From: faezehzafarbakhsh Date: Sat, 16 May 2026 14:36:09 +0330 Subject: [PATCH 33/61] fix code --- .../app/Admin/Controllers/ExpertManagementController.php | 2 ++ .../Admin/Requests/PermissionManagement/UpdateRequest.php | 2 +- backend/app/Admin/Requests/RoleManagement/StoreRequest.php | 4 ++-- backend/app/Admin/Requests/RoleManagement/UpdateRequest.php | 6 +++--- backend/app/Admin/Resources/ExpertManagementResource.php | 4 ++-- backend/app/Models/Expert.php | 3 +++ 6 files changed, 13 insertions(+), 8 deletions(-) diff --git a/backend/app/Admin/Controllers/ExpertManagementController.php b/backend/app/Admin/Controllers/ExpertManagementController.php index 6591a8a8..bc49f18f 100644 --- a/backend/app/Admin/Controllers/ExpertManagementController.php +++ b/backend/app/Admin/Controllers/ExpertManagementController.php @@ -67,6 +67,8 @@ class ExpertManagementController extends Controller */ public function show(Expert $expert): JsonResponse { + $expert->load('roles', 'permissions'); + return $this->successResponse( new ExpertManagementResource($expert) ); diff --git a/backend/app/Admin/Requests/PermissionManagement/UpdateRequest.php b/backend/app/Admin/Requests/PermissionManagement/UpdateRequest.php index 529ec458..9bcdf8b3 100644 --- a/backend/app/Admin/Requests/PermissionManagement/UpdateRequest.php +++ b/backend/app/Admin/Requests/PermissionManagement/UpdateRequest.php @@ -27,7 +27,7 @@ class UpdateRequest extends FormRequest public function rules(): array { return [ - 'name' => ['required', 'string', 'max:255', Rule::unique('permission', 'name')->ignore($this->permission->id)], + 'name' => ['required', 'string', 'max:255', Rule::unique('permissions', 'name')->ignore($this->permission->id)], ]; } } diff --git a/backend/app/Admin/Requests/RoleManagement/StoreRequest.php b/backend/app/Admin/Requests/RoleManagement/StoreRequest.php index b30125a9..6318879c 100644 --- a/backend/app/Admin/Requests/RoleManagement/StoreRequest.php +++ b/backend/app/Admin/Requests/RoleManagement/StoreRequest.php @@ -24,8 +24,8 @@ class StoreRequest extends FormRequest { return [ 'name' => 'required|string|unique:roles,name', - 'permissions' => 'required|array', - 'permissions.*.name' => 'required|string|exists:permissions,name', + 'permissions' => 'array', + 'permissions.*' => 'integer|exists:permissions,id', ]; } } diff --git a/backend/app/Admin/Requests/RoleManagement/UpdateRequest.php b/backend/app/Admin/Requests/RoleManagement/UpdateRequest.php index 4dd1cbf0..5b0058c2 100644 --- a/backend/app/Admin/Requests/RoleManagement/UpdateRequest.php +++ b/backend/app/Admin/Requests/RoleManagement/UpdateRequest.php @@ -27,9 +27,9 @@ class UpdateRequest extends FormRequest public function rules(): array { return [ - 'name' => ['required', 'string', 'max:255', Rule::unique('roles', 'name')->ignore($this->role->id)], - 'permissions' => 'required|array', - 'permissions.*.name' => 'required|string|exists:permissions,name', + 'name' => ['string', 'max:255', Rule::unique('roles', 'name')->ignore($this->role->id)], + 'permissions' => 'array', + 'permissions.*' => 'integer|exists:permissions,id', ]; } } diff --git a/backend/app/Admin/Resources/ExpertManagementResource.php b/backend/app/Admin/Resources/ExpertManagementResource.php index 788c0c93..2de7efb1 100644 --- a/backend/app/Admin/Resources/ExpertManagementResource.php +++ b/backend/app/Admin/Resources/ExpertManagementResource.php @@ -28,11 +28,11 @@ class ExpertManagementResource extends JsonResource 'phone_number' => $this->phone_number, 'province_name' => $this->province_name, 'city_name' => $this->city_name, - 'roles' => $this->roles() + 'roles' => $this->resource->roles ->pluck('name') ->values(), - 'permissions' => $this->getAllPermissions() + 'permissions' => $this->resource->getAllPermissions() ->pluck('name') ->values(), ]; diff --git a/backend/app/Models/Expert.php b/backend/app/Models/Expert.php index b55f8f55..8003184b 100644 --- a/backend/app/Models/Expert.php +++ b/backend/app/Models/Expert.php @@ -5,6 +5,7 @@ namespace App\Models; use Database\Factories\ExpertFactory; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use Spatie\Permission\Traits\HasRoles; /** * @method roles() @@ -14,6 +15,8 @@ class Expert extends Model { /** @use HasFactory */ use HasFactory; + use HasRoles; protected $guarded = ['id']; + protected string $guard_name = 'web'; } -- 2.49.1 From 7c82556d93923acb19eded0575a5f2d632b66850 Mon Sep 17 00:00:00 2001 From: faezehzafarbakhsh Date: Sat, 16 May 2026 14:38:39 +0330 Subject: [PATCH 34/61] fix code2 --- backend/app/Admin/Controllers/ExpertManagementController.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/backend/app/Admin/Controllers/ExpertManagementController.php b/backend/app/Admin/Controllers/ExpertManagementController.php index bc49f18f..6591a8a8 100644 --- a/backend/app/Admin/Controllers/ExpertManagementController.php +++ b/backend/app/Admin/Controllers/ExpertManagementController.php @@ -67,8 +67,6 @@ class ExpertManagementController extends Controller */ public function show(Expert $expert): JsonResponse { - $expert->load('roles', 'permissions'); - return $this->successResponse( new ExpertManagementResource($expert) ); -- 2.49.1 From 9fcd8045db6010d38b9c76a43e82cf8525f28895 Mon Sep 17 00:00:00 2001 From: faezehzafarbakhsh Date: Sat, 16 May 2026 14:57:34 +0330 Subject: [PATCH 35/61] fix code3 --- .../ExpertManagementController.php | 3 +- .../Resources/ExpertManagementResource.php | 41 ------------------- 2 files changed, 1 insertion(+), 43 deletions(-) delete mode 100644 backend/app/Admin/Resources/ExpertManagementResource.php diff --git a/backend/app/Admin/Controllers/ExpertManagementController.php b/backend/app/Admin/Controllers/ExpertManagementController.php index 6591a8a8..a3e0c989 100644 --- a/backend/app/Admin/Controllers/ExpertManagementController.php +++ b/backend/app/Admin/Controllers/ExpertManagementController.php @@ -67,8 +67,7 @@ class ExpertManagementController extends Controller */ public function show(Expert $expert): JsonResponse { - return $this->successResponse( - new ExpertManagementResource($expert) + return $this->successResponse($expert->load(['roles','permissions']) ); } diff --git a/backend/app/Admin/Resources/ExpertManagementResource.php b/backend/app/Admin/Resources/ExpertManagementResource.php deleted file mode 100644 index 2de7efb1..00000000 --- a/backend/app/Admin/Resources/ExpertManagementResource.php +++ /dev/null @@ -1,41 +0,0 @@ - - */ - public function toArray(Request $request): array - { - return [ - 'id' => $this->id, - 'username' => $this->username, - 'first_name' => $this->first_name, - 'last_name' => $this->last_name, - 'national_id' => $this->national_id, - 'email' => $this->email, - 'phone_number' => $this->phone_number, - 'province_name' => $this->province_name, - 'city_name' => $this->city_name, - 'roles' => $this->resource->roles - ->pluck('name') - ->values(), - - 'permissions' => $this->resource->getAllPermissions() - ->pluck('name') - ->values(), - ]; - } -} - -- 2.49.1 From 19abb28acf4d7b3105c4f7f23c11f1cd164e6da9 Mon Sep 17 00:00:00 2001 From: amirghasempoor Date: Sun, 17 May 2026 08:49:28 +0330 Subject: [PATCH 36/61] upload files --- backend/.dockerignore | 0 backend/.editorconfig | 0 backend/.env.example | 0 backend/.gitattributes | 0 backend/.gitignore | 0 backend/.npmrc | 0 backend/Dockerfile | 0 backend/Dockerfile.production | 0 backend/README.md | 0 .../app/Admin/Controllers/AuthController.php | 0 .../Controllers/DocumentationController.php | 0 .../ExpertManagementController.php | 0 .../PermissionManagementController.php | 0 .../Admin/Controllers/ProfileController.php | 0 .../Controllers/UserManagementController.php | 0 .../app/Admin/Requests/Auth/LoginRequest.php | 0 .../Admin/Requests/Auth/RegisterRequest.php | 0 .../Requests/Documentation/RejectRequest.php | 0 .../ExpertManagement/StoreRequest.php | 0 .../ExpertManagement/UpdateRequest.php | 0 .../PermissionManagement/StoreRequest.php | 0 .../PermissionManagement/UpdateRequest.php | 0 .../Profile/ChangePasswordRequest.php | 0 .../Requests/RoleManagement/StoreRequest.php | 0 .../Requests/RoleManagement/UpdateRequest.php | 0 .../Requests/UserManagement/StoreRequest.php | 0 .../Requests/UserManagement/UpdateRequest.php | 0 .../Resources/ExpertManagementResource.php | 0 .../app/Admin/Resources/ExpertResource.php | 0 backend/app/Facades/DataTable/DataTable.php | 0 .../app/Facades/DataTable/DataTableFacade.php | 0 backend/app/Facades/File/File.php | 0 backend/app/Facades/File/FileFacade.php | 0 backend/app/Http/Controllers/Controller.php | 0 backend/app/Mail/LoginNotificationMail.php | 0 backend/app/Models/City.php | 0 backend/app/Models/Document.php | 0 backend/app/Models/Expert.php | 0 backend/app/Models/Province.php | 0 backend/app/Models/User.php | 12 +++++ backend/app/Providers/AppServiceProvider.php | 2 + .../Providers/DataTableServiceProvider.php | 0 backend/app/Providers/FileServiceProvider.php | 0 .../app/Services/DataTable/DataTableInput.php | 0 .../Services/DataTable/DataTableService.php | 0 .../app/Services/DataTable/Enums/DataType.php | 0 .../Services/DataTable/Enums/SearchType.php | 0 .../Exceptions/InvalidFilterException.php | 0 .../Exceptions/InvalidParameterInterface.php | 0 .../Exceptions/InvalidRelationException.php | 0 .../Exceptions/InvalidSortingException.php | 0 .../Services/DataTable/Filter/ApplyFilter.php | 0 .../app/Services/DataTable/Filter/Filter.php | 0 .../Filter/SearchFunctions/FilterBetween.php | 0 .../Filter/SearchFunctions/FilterContains.php | 0 .../Filter/SearchFunctions/FilterEquals.php | 0 .../SearchFunctions/FilterGreaterThan.php | 0 .../FilterGreaterThanOrEqual.php | 0 .../Filter/SearchFunctions/FilterLessThan.php | 0 .../SearchFunctions/FilterLessThanOrEqual.php | 0 .../SearchFunctions/FilterNotEquals.php | 0 .../Filter/SearchFunctions/SearchFilter.php | 0 .../app/Services/DataTable/Sort/ApplySort.php | 0 backend/app/Services/DataTable/Sort/Sort.php | 0 .../DataTable/Validators/FilterValidator.php | 0 .../Validators/RelationValidator.php | 0 .../DataTable/Validators/SortingValidator.php | 0 .../app/User/Controllers/AuthController.php | 0 .../Dashboard/ConfirmController.php | 14 ----- .../Dashboard/DocumentationController.php | 24 --------- .../Controllers/DocumentationController.php | 42 +++++++++++++++ .../User/Controllers/ProfileController.php | 0 .../app/User/Requests/Auth/LoginRequest.php | 0 .../User/Requests/Auth/RegisterRequest.php | 0 .../Requests/Documentation/EditRequest.php | 31 +++++++++++ .../Requests/Documentation/UploadRequest.php | 31 +++++++++++ .../Profile/ChangePasswordRequest.php | 0 .../app/User/Requests/Profile/EditRequest.php | 0 backend/app/User/Resources/UserResource.php | 0 .../Services/FIB/AuthorizationService.php | 0 .../Services/FIB/CancelPaymentService.php | 0 .../FIB/CheckPaymentStatusService.php | 0 .../Services/FIB/CreatePaymentService.php | 0 .../app/User/Services/FIB/RefundService.php | 0 backend/artisan | 0 backend/bootstrap/providers.php | 0 backend/composer.json | 0 backend/composer.lock | 0 backend/config/app.php | 0 backend/config/cache.php | 0 backend/config/database.php | 0 backend/config/fib.php | 0 backend/config/filesystems.php | 0 backend/config/logging.php | 0 backend/config/mail.php | 0 backend/config/passport.php | 0 backend/config/permission.php | 0 backend/config/queue.php | 0 backend/config/services.php | 0 backend/database/.gitignore | 0 backend/database/factories/CityFactory.php | 0 .../database/factories/DocumentFactory.php | 0 backend/database/factories/ExpertFactory.php | 0 .../database/factories/ProvinceFactory.php | 0 ...01_01_00_000000_create_provinces_table.php | 0 .../0001_01_00_000001_create_cities_table.php | 0 .../0001_01_01_000001_create_cache_table.php | 0 .../0001_01_01_000002_create_jobs_table.php | 0 ...7_122415_create_oauth_auth_codes_table.php | 0 ...22416_create_oauth_access_tokens_table.php | 0 ...2417_create_oauth_refresh_tokens_table.php | 0 ...4_27_122418_create_oauth_clients_table.php | 0 ...122419_create_oauth_device_codes_table.php | 0 ..._05_10_123913_create_permission_tables.php | 0 ...26_05_12_053258_create_documents_table.php | 0 ...2026_05_12_074230_create_experts_table.php | 0 ...05_13_054404_add_column_to_users_table.php | 0 backend/database/seeders/CitySeeder.php | 0 backend/database/seeders/DatabaseSeeder.php | 2 +- backend/database/seeders/DocumentSeeder.php | 0 backend/database/seeders/ExpertSeeder.php | 0 backend/database/seeders/ProvinceSeeder.php | 0 backend/package.json | 0 backend/phpunit.xml | 0 backend/public/.htaccess | 0 backend/public/favicon.ico | 0 backend/public/index.php | 0 backend/public/robots.txt | 0 backend/resources/css/app.css | 0 backend/resources/js/app.js | 0 backend/resources/lang/ar/notifications.php | 0 backend/resources/lang/en/auth.php | 0 backend/resources/lang/en/messages.php | 0 backend/resources/lang/en/pagination.php | 0 backend/resources/lang/en/passwords.php | 0 backend/resources/lang/en/validation.php | 0 backend/resources/lang/fa/auth.php | 0 backend/resources/lang/fa/messages.php | 0 backend/resources/lang/fa/pagination.php | 0 backend/resources/lang/fa/passwords.php | 0 backend/resources/lang/fa/validation.php | 0 .../views/emails/login-notification.blade.php | 0 backend/resources/views/welcome.blade.php | 0 backend/routes/console.php | 0 backend/routes/expert.php | 53 ++++++++++++++++++- backend/routes/web.php | 46 ---------------- backend/tests/Feature/ExampleTest.php | 0 backend/tests/Pest.php | 0 backend/tests/TestCase.php | 0 backend/tests/Unit/ExampleTest.php | 0 backend/vite.config.js | 0 151 files changed, 170 insertions(+), 87 deletions(-) mode change 100644 => 100755 backend/.dockerignore mode change 100644 => 100755 backend/.editorconfig mode change 100644 => 100755 backend/.env.example mode change 100644 => 100755 backend/.gitattributes mode change 100644 => 100755 backend/.gitignore mode change 100644 => 100755 backend/.npmrc mode change 100644 => 100755 backend/Dockerfile mode change 100644 => 100755 backend/Dockerfile.production mode change 100644 => 100755 backend/README.md mode change 100644 => 100755 backend/app/Admin/Controllers/AuthController.php mode change 100644 => 100755 backend/app/Admin/Controllers/DocumentationController.php mode change 100644 => 100755 backend/app/Admin/Controllers/ExpertManagementController.php mode change 100644 => 100755 backend/app/Admin/Controllers/PermissionManagementController.php mode change 100644 => 100755 backend/app/Admin/Controllers/ProfileController.php mode change 100644 => 100755 backend/app/Admin/Controllers/UserManagementController.php mode change 100644 => 100755 backend/app/Admin/Requests/Auth/LoginRequest.php mode change 100644 => 100755 backend/app/Admin/Requests/Auth/RegisterRequest.php mode change 100644 => 100755 backend/app/Admin/Requests/Documentation/RejectRequest.php mode change 100644 => 100755 backend/app/Admin/Requests/ExpertManagement/StoreRequest.php mode change 100644 => 100755 backend/app/Admin/Requests/ExpertManagement/UpdateRequest.php mode change 100644 => 100755 backend/app/Admin/Requests/PermissionManagement/StoreRequest.php mode change 100644 => 100755 backend/app/Admin/Requests/PermissionManagement/UpdateRequest.php mode change 100644 => 100755 backend/app/Admin/Requests/Profile/ChangePasswordRequest.php mode change 100644 => 100755 backend/app/Admin/Requests/RoleManagement/StoreRequest.php mode change 100644 => 100755 backend/app/Admin/Requests/RoleManagement/UpdateRequest.php mode change 100644 => 100755 backend/app/Admin/Requests/UserManagement/StoreRequest.php mode change 100644 => 100755 backend/app/Admin/Requests/UserManagement/UpdateRequest.php mode change 100644 => 100755 backend/app/Admin/Resources/ExpertManagementResource.php mode change 100644 => 100755 backend/app/Admin/Resources/ExpertResource.php mode change 100644 => 100755 backend/app/Facades/DataTable/DataTable.php mode change 100644 => 100755 backend/app/Facades/DataTable/DataTableFacade.php mode change 100644 => 100755 backend/app/Facades/File/File.php mode change 100644 => 100755 backend/app/Facades/File/FileFacade.php mode change 100644 => 100755 backend/app/Http/Controllers/Controller.php mode change 100644 => 100755 backend/app/Mail/LoginNotificationMail.php mode change 100644 => 100755 backend/app/Models/City.php mode change 100644 => 100755 backend/app/Models/Document.php mode change 100644 => 100755 backend/app/Models/Expert.php mode change 100644 => 100755 backend/app/Models/Province.php mode change 100644 => 100755 backend/app/Providers/DataTableServiceProvider.php mode change 100644 => 100755 backend/app/Providers/FileServiceProvider.php mode change 100644 => 100755 backend/app/Services/DataTable/DataTableInput.php mode change 100644 => 100755 backend/app/Services/DataTable/DataTableService.php mode change 100644 => 100755 backend/app/Services/DataTable/Enums/DataType.php mode change 100644 => 100755 backend/app/Services/DataTable/Enums/SearchType.php mode change 100644 => 100755 backend/app/Services/DataTable/Exceptions/InvalidFilterException.php mode change 100644 => 100755 backend/app/Services/DataTable/Exceptions/InvalidParameterInterface.php mode change 100644 => 100755 backend/app/Services/DataTable/Exceptions/InvalidRelationException.php mode change 100644 => 100755 backend/app/Services/DataTable/Exceptions/InvalidSortingException.php mode change 100644 => 100755 backend/app/Services/DataTable/Filter/ApplyFilter.php mode change 100644 => 100755 backend/app/Services/DataTable/Filter/Filter.php mode change 100644 => 100755 backend/app/Services/DataTable/Filter/SearchFunctions/FilterBetween.php mode change 100644 => 100755 backend/app/Services/DataTable/Filter/SearchFunctions/FilterContains.php mode change 100644 => 100755 backend/app/Services/DataTable/Filter/SearchFunctions/FilterEquals.php mode change 100644 => 100755 backend/app/Services/DataTable/Filter/SearchFunctions/FilterGreaterThan.php mode change 100644 => 100755 backend/app/Services/DataTable/Filter/SearchFunctions/FilterGreaterThanOrEqual.php mode change 100644 => 100755 backend/app/Services/DataTable/Filter/SearchFunctions/FilterLessThan.php mode change 100644 => 100755 backend/app/Services/DataTable/Filter/SearchFunctions/FilterLessThanOrEqual.php mode change 100644 => 100755 backend/app/Services/DataTable/Filter/SearchFunctions/FilterNotEquals.php mode change 100644 => 100755 backend/app/Services/DataTable/Filter/SearchFunctions/SearchFilter.php mode change 100644 => 100755 backend/app/Services/DataTable/Sort/ApplySort.php mode change 100644 => 100755 backend/app/Services/DataTable/Sort/Sort.php mode change 100644 => 100755 backend/app/Services/DataTable/Validators/FilterValidator.php mode change 100644 => 100755 backend/app/Services/DataTable/Validators/RelationValidator.php mode change 100644 => 100755 backend/app/Services/DataTable/Validators/SortingValidator.php mode change 100644 => 100755 backend/app/User/Controllers/AuthController.php delete mode 100755 backend/app/User/Controllers/Dashboard/ConfirmController.php delete mode 100755 backend/app/User/Controllers/Dashboard/DocumentationController.php create mode 100755 backend/app/User/Controllers/DocumentationController.php mode change 100644 => 100755 backend/app/User/Controllers/ProfileController.php mode change 100644 => 100755 backend/app/User/Requests/Auth/LoginRequest.php mode change 100644 => 100755 backend/app/User/Requests/Auth/RegisterRequest.php create mode 100755 backend/app/User/Requests/Documentation/EditRequest.php create mode 100755 backend/app/User/Requests/Documentation/UploadRequest.php mode change 100644 => 100755 backend/app/User/Requests/Profile/ChangePasswordRequest.php mode change 100644 => 100755 backend/app/User/Requests/Profile/EditRequest.php mode change 100644 => 100755 backend/app/User/Resources/UserResource.php mode change 100644 => 100755 backend/app/User/Services/FIB/AuthorizationService.php mode change 100644 => 100755 backend/app/User/Services/FIB/CancelPaymentService.php mode change 100644 => 100755 backend/app/User/Services/FIB/CheckPaymentStatusService.php mode change 100644 => 100755 backend/app/User/Services/FIB/CreatePaymentService.php mode change 100644 => 100755 backend/app/User/Services/FIB/RefundService.php mode change 100644 => 100755 backend/artisan mode change 100644 => 100755 backend/bootstrap/providers.php mode change 100644 => 100755 backend/composer.json mode change 100644 => 100755 backend/composer.lock mode change 100644 => 100755 backend/config/app.php mode change 100644 => 100755 backend/config/cache.php mode change 100644 => 100755 backend/config/database.php mode change 100644 => 100755 backend/config/fib.php mode change 100644 => 100755 backend/config/filesystems.php mode change 100644 => 100755 backend/config/logging.php mode change 100644 => 100755 backend/config/mail.php mode change 100644 => 100755 backend/config/passport.php mode change 100644 => 100755 backend/config/permission.php mode change 100644 => 100755 backend/config/queue.php mode change 100644 => 100755 backend/config/services.php mode change 100644 => 100755 backend/database/.gitignore mode change 100644 => 100755 backend/database/factories/CityFactory.php mode change 100644 => 100755 backend/database/factories/DocumentFactory.php mode change 100644 => 100755 backend/database/factories/ExpertFactory.php mode change 100644 => 100755 backend/database/factories/ProvinceFactory.php mode change 100644 => 100755 backend/database/migrations/0001_01_00_000000_create_provinces_table.php mode change 100644 => 100755 backend/database/migrations/0001_01_00_000001_create_cities_table.php mode change 100644 => 100755 backend/database/migrations/0001_01_01_000001_create_cache_table.php mode change 100644 => 100755 backend/database/migrations/0001_01_01_000002_create_jobs_table.php mode change 100644 => 100755 backend/database/migrations/2026_04_27_122415_create_oauth_auth_codes_table.php mode change 100644 => 100755 backend/database/migrations/2026_04_27_122416_create_oauth_access_tokens_table.php mode change 100644 => 100755 backend/database/migrations/2026_04_27_122417_create_oauth_refresh_tokens_table.php mode change 100644 => 100755 backend/database/migrations/2026_04_27_122418_create_oauth_clients_table.php mode change 100644 => 100755 backend/database/migrations/2026_04_27_122419_create_oauth_device_codes_table.php mode change 100644 => 100755 backend/database/migrations/2026_05_10_123913_create_permission_tables.php mode change 100644 => 100755 backend/database/migrations/2026_05_12_053258_create_documents_table.php mode change 100644 => 100755 backend/database/migrations/2026_05_12_074230_create_experts_table.php mode change 100644 => 100755 backend/database/migrations/2026_05_13_054404_add_column_to_users_table.php mode change 100644 => 100755 backend/database/seeders/CitySeeder.php mode change 100644 => 100755 backend/database/seeders/DocumentSeeder.php mode change 100644 => 100755 backend/database/seeders/ExpertSeeder.php mode change 100644 => 100755 backend/database/seeders/ProvinceSeeder.php mode change 100644 => 100755 backend/package.json mode change 100644 => 100755 backend/phpunit.xml mode change 100644 => 100755 backend/public/.htaccess mode change 100644 => 100755 backend/public/favicon.ico mode change 100644 => 100755 backend/public/index.php mode change 100644 => 100755 backend/public/robots.txt mode change 100644 => 100755 backend/resources/css/app.css mode change 100644 => 100755 backend/resources/js/app.js mode change 100644 => 100755 backend/resources/lang/ar/notifications.php mode change 100644 => 100755 backend/resources/lang/en/auth.php mode change 100644 => 100755 backend/resources/lang/en/messages.php mode change 100644 => 100755 backend/resources/lang/en/pagination.php mode change 100644 => 100755 backend/resources/lang/en/passwords.php mode change 100644 => 100755 backend/resources/lang/en/validation.php mode change 100644 => 100755 backend/resources/lang/fa/auth.php mode change 100644 => 100755 backend/resources/lang/fa/messages.php mode change 100644 => 100755 backend/resources/lang/fa/pagination.php mode change 100644 => 100755 backend/resources/lang/fa/passwords.php mode change 100644 => 100755 backend/resources/lang/fa/validation.php mode change 100644 => 100755 backend/resources/views/emails/login-notification.blade.php mode change 100644 => 100755 backend/resources/views/welcome.blade.php mode change 100644 => 100755 backend/routes/console.php mode change 100644 => 100755 backend/tests/Feature/ExampleTest.php mode change 100644 => 100755 backend/tests/Pest.php mode change 100644 => 100755 backend/tests/TestCase.php mode change 100644 => 100755 backend/tests/Unit/ExampleTest.php mode change 100644 => 100755 backend/vite.config.js diff --git a/backend/.dockerignore b/backend/.dockerignore old mode 100644 new mode 100755 diff --git a/backend/.editorconfig b/backend/.editorconfig old mode 100644 new mode 100755 diff --git a/backend/.env.example b/backend/.env.example old mode 100644 new mode 100755 diff --git a/backend/.gitattributes b/backend/.gitattributes old mode 100644 new mode 100755 diff --git a/backend/.gitignore b/backend/.gitignore old mode 100644 new mode 100755 diff --git a/backend/.npmrc b/backend/.npmrc old mode 100644 new mode 100755 diff --git a/backend/Dockerfile b/backend/Dockerfile old mode 100644 new mode 100755 diff --git a/backend/Dockerfile.production b/backend/Dockerfile.production old mode 100644 new mode 100755 diff --git a/backend/README.md b/backend/README.md old mode 100644 new mode 100755 diff --git a/backend/app/Admin/Controllers/AuthController.php b/backend/app/Admin/Controllers/AuthController.php old mode 100644 new mode 100755 diff --git a/backend/app/Admin/Controllers/DocumentationController.php b/backend/app/Admin/Controllers/DocumentationController.php old mode 100644 new mode 100755 diff --git a/backend/app/Admin/Controllers/ExpertManagementController.php b/backend/app/Admin/Controllers/ExpertManagementController.php old mode 100644 new mode 100755 diff --git a/backend/app/Admin/Controllers/PermissionManagementController.php b/backend/app/Admin/Controllers/PermissionManagementController.php old mode 100644 new mode 100755 diff --git a/backend/app/Admin/Controllers/ProfileController.php b/backend/app/Admin/Controllers/ProfileController.php old mode 100644 new mode 100755 diff --git a/backend/app/Admin/Controllers/UserManagementController.php b/backend/app/Admin/Controllers/UserManagementController.php old mode 100644 new mode 100755 diff --git a/backend/app/Admin/Requests/Auth/LoginRequest.php b/backend/app/Admin/Requests/Auth/LoginRequest.php old mode 100644 new mode 100755 diff --git a/backend/app/Admin/Requests/Auth/RegisterRequest.php b/backend/app/Admin/Requests/Auth/RegisterRequest.php old mode 100644 new mode 100755 diff --git a/backend/app/Admin/Requests/Documentation/RejectRequest.php b/backend/app/Admin/Requests/Documentation/RejectRequest.php old mode 100644 new mode 100755 diff --git a/backend/app/Admin/Requests/ExpertManagement/StoreRequest.php b/backend/app/Admin/Requests/ExpertManagement/StoreRequest.php old mode 100644 new mode 100755 diff --git a/backend/app/Admin/Requests/ExpertManagement/UpdateRequest.php b/backend/app/Admin/Requests/ExpertManagement/UpdateRequest.php old mode 100644 new mode 100755 diff --git a/backend/app/Admin/Requests/PermissionManagement/StoreRequest.php b/backend/app/Admin/Requests/PermissionManagement/StoreRequest.php old mode 100644 new mode 100755 diff --git a/backend/app/Admin/Requests/PermissionManagement/UpdateRequest.php b/backend/app/Admin/Requests/PermissionManagement/UpdateRequest.php old mode 100644 new mode 100755 diff --git a/backend/app/Admin/Requests/Profile/ChangePasswordRequest.php b/backend/app/Admin/Requests/Profile/ChangePasswordRequest.php old mode 100644 new mode 100755 diff --git a/backend/app/Admin/Requests/RoleManagement/StoreRequest.php b/backend/app/Admin/Requests/RoleManagement/StoreRequest.php old mode 100644 new mode 100755 diff --git a/backend/app/Admin/Requests/RoleManagement/UpdateRequest.php b/backend/app/Admin/Requests/RoleManagement/UpdateRequest.php old mode 100644 new mode 100755 diff --git a/backend/app/Admin/Requests/UserManagement/StoreRequest.php b/backend/app/Admin/Requests/UserManagement/StoreRequest.php old mode 100644 new mode 100755 diff --git a/backend/app/Admin/Requests/UserManagement/UpdateRequest.php b/backend/app/Admin/Requests/UserManagement/UpdateRequest.php old mode 100644 new mode 100755 diff --git a/backend/app/Admin/Resources/ExpertManagementResource.php b/backend/app/Admin/Resources/ExpertManagementResource.php old mode 100644 new mode 100755 diff --git a/backend/app/Admin/Resources/ExpertResource.php b/backend/app/Admin/Resources/ExpertResource.php old mode 100644 new mode 100755 diff --git a/backend/app/Facades/DataTable/DataTable.php b/backend/app/Facades/DataTable/DataTable.php old mode 100644 new mode 100755 diff --git a/backend/app/Facades/DataTable/DataTableFacade.php b/backend/app/Facades/DataTable/DataTableFacade.php old mode 100644 new mode 100755 diff --git a/backend/app/Facades/File/File.php b/backend/app/Facades/File/File.php old mode 100644 new mode 100755 diff --git a/backend/app/Facades/File/FileFacade.php b/backend/app/Facades/File/FileFacade.php old mode 100644 new mode 100755 diff --git a/backend/app/Http/Controllers/Controller.php b/backend/app/Http/Controllers/Controller.php old mode 100644 new mode 100755 diff --git a/backend/app/Mail/LoginNotificationMail.php b/backend/app/Mail/LoginNotificationMail.php old mode 100644 new mode 100755 diff --git a/backend/app/Models/City.php b/backend/app/Models/City.php old mode 100644 new mode 100755 diff --git a/backend/app/Models/Document.php b/backend/app/Models/Document.php old mode 100644 new mode 100755 diff --git a/backend/app/Models/Expert.php b/backend/app/Models/Expert.php old mode 100644 new mode 100755 diff --git a/backend/app/Models/Province.php b/backend/app/Models/Province.php old mode 100644 new mode 100755 diff --git a/backend/app/Models/User.php b/backend/app/Models/User.php index ef9670d0..20e0ad4a 100755 --- a/backend/app/Models/User.php +++ b/backend/app/Models/User.php @@ -10,6 +10,8 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\MorphToMany; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; +use Illuminate\Support\Facades\Hash; +use Laravel\Passport\Bridge\Client; use Laravel\Passport\HasApiTokens; #[Guarded(['id'])] @@ -24,4 +26,14 @@ class User extends Authenticatable { return $this->morphToMany(Document::class, 'documentable'); } + + public function validateForPassportPasswordGrant(string $password): bool + { + return Hash::check($password, $this->password); + } + + public function findForPassport(string $username, Client $client): User + { + return $this->where('username', $username)->first(); + } } diff --git a/backend/app/Providers/AppServiceProvider.php b/backend/app/Providers/AppServiceProvider.php index 1039668a..c542af2e 100755 --- a/backend/app/Providers/AppServiceProvider.php +++ b/backend/app/Providers/AppServiceProvider.php @@ -22,6 +22,8 @@ class AppServiceProvider extends ServiceProvider public function boot(): void { Passport::enablePasswordGrant(); + Passport::tokensExpireIn(CarbonInterval::minutes(1)); + Passport::refreshTokensExpireIn(CarbonInterval::minutes(5)); Passport::personalAccessTokensExpireIn(CarbonInterval::minutes(1)); } } diff --git a/backend/app/Providers/DataTableServiceProvider.php b/backend/app/Providers/DataTableServiceProvider.php old mode 100644 new mode 100755 diff --git a/backend/app/Providers/FileServiceProvider.php b/backend/app/Providers/FileServiceProvider.php old mode 100644 new mode 100755 diff --git a/backend/app/Services/DataTable/DataTableInput.php b/backend/app/Services/DataTable/DataTableInput.php old mode 100644 new mode 100755 diff --git a/backend/app/Services/DataTable/DataTableService.php b/backend/app/Services/DataTable/DataTableService.php old mode 100644 new mode 100755 diff --git a/backend/app/Services/DataTable/Enums/DataType.php b/backend/app/Services/DataTable/Enums/DataType.php old mode 100644 new mode 100755 diff --git a/backend/app/Services/DataTable/Enums/SearchType.php b/backend/app/Services/DataTable/Enums/SearchType.php old mode 100644 new mode 100755 diff --git a/backend/app/Services/DataTable/Exceptions/InvalidFilterException.php b/backend/app/Services/DataTable/Exceptions/InvalidFilterException.php old mode 100644 new mode 100755 diff --git a/backend/app/Services/DataTable/Exceptions/InvalidParameterInterface.php b/backend/app/Services/DataTable/Exceptions/InvalidParameterInterface.php old mode 100644 new mode 100755 diff --git a/backend/app/Services/DataTable/Exceptions/InvalidRelationException.php b/backend/app/Services/DataTable/Exceptions/InvalidRelationException.php old mode 100644 new mode 100755 diff --git a/backend/app/Services/DataTable/Exceptions/InvalidSortingException.php b/backend/app/Services/DataTable/Exceptions/InvalidSortingException.php old mode 100644 new mode 100755 diff --git a/backend/app/Services/DataTable/Filter/ApplyFilter.php b/backend/app/Services/DataTable/Filter/ApplyFilter.php old mode 100644 new mode 100755 diff --git a/backend/app/Services/DataTable/Filter/Filter.php b/backend/app/Services/DataTable/Filter/Filter.php old mode 100644 new mode 100755 diff --git a/backend/app/Services/DataTable/Filter/SearchFunctions/FilterBetween.php b/backend/app/Services/DataTable/Filter/SearchFunctions/FilterBetween.php old mode 100644 new mode 100755 diff --git a/backend/app/Services/DataTable/Filter/SearchFunctions/FilterContains.php b/backend/app/Services/DataTable/Filter/SearchFunctions/FilterContains.php old mode 100644 new mode 100755 diff --git a/backend/app/Services/DataTable/Filter/SearchFunctions/FilterEquals.php b/backend/app/Services/DataTable/Filter/SearchFunctions/FilterEquals.php old mode 100644 new mode 100755 diff --git a/backend/app/Services/DataTable/Filter/SearchFunctions/FilterGreaterThan.php b/backend/app/Services/DataTable/Filter/SearchFunctions/FilterGreaterThan.php old mode 100644 new mode 100755 diff --git a/backend/app/Services/DataTable/Filter/SearchFunctions/FilterGreaterThanOrEqual.php b/backend/app/Services/DataTable/Filter/SearchFunctions/FilterGreaterThanOrEqual.php old mode 100644 new mode 100755 diff --git a/backend/app/Services/DataTable/Filter/SearchFunctions/FilterLessThan.php b/backend/app/Services/DataTable/Filter/SearchFunctions/FilterLessThan.php old mode 100644 new mode 100755 diff --git a/backend/app/Services/DataTable/Filter/SearchFunctions/FilterLessThanOrEqual.php b/backend/app/Services/DataTable/Filter/SearchFunctions/FilterLessThanOrEqual.php old mode 100644 new mode 100755 diff --git a/backend/app/Services/DataTable/Filter/SearchFunctions/FilterNotEquals.php b/backend/app/Services/DataTable/Filter/SearchFunctions/FilterNotEquals.php old mode 100644 new mode 100755 diff --git a/backend/app/Services/DataTable/Filter/SearchFunctions/SearchFilter.php b/backend/app/Services/DataTable/Filter/SearchFunctions/SearchFilter.php old mode 100644 new mode 100755 diff --git a/backend/app/Services/DataTable/Sort/ApplySort.php b/backend/app/Services/DataTable/Sort/ApplySort.php old mode 100644 new mode 100755 diff --git a/backend/app/Services/DataTable/Sort/Sort.php b/backend/app/Services/DataTable/Sort/Sort.php old mode 100644 new mode 100755 diff --git a/backend/app/Services/DataTable/Validators/FilterValidator.php b/backend/app/Services/DataTable/Validators/FilterValidator.php old mode 100644 new mode 100755 diff --git a/backend/app/Services/DataTable/Validators/RelationValidator.php b/backend/app/Services/DataTable/Validators/RelationValidator.php old mode 100644 new mode 100755 diff --git a/backend/app/Services/DataTable/Validators/SortingValidator.php b/backend/app/Services/DataTable/Validators/SortingValidator.php old mode 100644 new mode 100755 diff --git a/backend/app/User/Controllers/AuthController.php b/backend/app/User/Controllers/AuthController.php old mode 100644 new mode 100755 diff --git a/backend/app/User/Controllers/Dashboard/ConfirmController.php b/backend/app/User/Controllers/Dashboard/ConfirmController.php deleted file mode 100755 index 8fdc5909..00000000 --- a/backend/app/User/Controllers/Dashboard/ConfirmController.php +++ /dev/null @@ -1,14 +0,0 @@ -user(); + + foreach ($request->documents as $document) { + $user->documents()->create([ + 'title' => $document['title'], + 'url' => FileFacade::save($document['file'], "/{$user->id}/documents", $document['title']), + ]); + } + + return $this->successResponse(); + } + + public function edit(EditRequest $request): JsonResponse + { + $user = Auth::guard('web')->user(); + + foreach ($request->documents as $document) { + $user->documents()->sync(); + } + + return $this->successResponse(); + } +} diff --git a/backend/app/User/Controllers/ProfileController.php b/backend/app/User/Controllers/ProfileController.php old mode 100644 new mode 100755 diff --git a/backend/app/User/Requests/Auth/LoginRequest.php b/backend/app/User/Requests/Auth/LoginRequest.php old mode 100644 new mode 100755 diff --git a/backend/app/User/Requests/Auth/RegisterRequest.php b/backend/app/User/Requests/Auth/RegisterRequest.php old mode 100644 new mode 100755 diff --git a/backend/app/User/Requests/Documentation/EditRequest.php b/backend/app/User/Requests/Documentation/EditRequest.php new file mode 100755 index 00000000..25c3e1d2 --- /dev/null +++ b/backend/app/User/Requests/Documentation/EditRequest.php @@ -0,0 +1,31 @@ + + */ + public function rules(): array + { + return [ + 'documents' => 'required|array', + 'documents.*.file' => 'required|file|mimes:pdf,jpg,jpeg,png|max:2048', + 'documents.*.title' => 'required|string', + ]; + } +} diff --git a/backend/app/User/Requests/Documentation/UploadRequest.php b/backend/app/User/Requests/Documentation/UploadRequest.php new file mode 100755 index 00000000..b832c916 --- /dev/null +++ b/backend/app/User/Requests/Documentation/UploadRequest.php @@ -0,0 +1,31 @@ + + */ + public function rules(): array + { + return [ + 'documents' => 'required|array', + 'documents.*.file' => 'required|file|mimes:pdf,jpg,jpeg,png|max:2048', + 'documents.*.title' => 'required|string', + ]; + } +} diff --git a/backend/app/User/Requests/Profile/ChangePasswordRequest.php b/backend/app/User/Requests/Profile/ChangePasswordRequest.php old mode 100644 new mode 100755 diff --git a/backend/app/User/Requests/Profile/EditRequest.php b/backend/app/User/Requests/Profile/EditRequest.php old mode 100644 new mode 100755 diff --git a/backend/app/User/Resources/UserResource.php b/backend/app/User/Resources/UserResource.php old mode 100644 new mode 100755 diff --git a/backend/app/User/Services/FIB/AuthorizationService.php b/backend/app/User/Services/FIB/AuthorizationService.php old mode 100644 new mode 100755 diff --git a/backend/app/User/Services/FIB/CancelPaymentService.php b/backend/app/User/Services/FIB/CancelPaymentService.php old mode 100644 new mode 100755 diff --git a/backend/app/User/Services/FIB/CheckPaymentStatusService.php b/backend/app/User/Services/FIB/CheckPaymentStatusService.php old mode 100644 new mode 100755 diff --git a/backend/app/User/Services/FIB/CreatePaymentService.php b/backend/app/User/Services/FIB/CreatePaymentService.php old mode 100644 new mode 100755 diff --git a/backend/app/User/Services/FIB/RefundService.php b/backend/app/User/Services/FIB/RefundService.php old mode 100644 new mode 100755 diff --git a/backend/artisan b/backend/artisan old mode 100644 new mode 100755 diff --git a/backend/bootstrap/providers.php b/backend/bootstrap/providers.php old mode 100644 new mode 100755 diff --git a/backend/composer.json b/backend/composer.json old mode 100644 new mode 100755 diff --git a/backend/composer.lock b/backend/composer.lock old mode 100644 new mode 100755 diff --git a/backend/config/app.php b/backend/config/app.php old mode 100644 new mode 100755 diff --git a/backend/config/cache.php b/backend/config/cache.php old mode 100644 new mode 100755 diff --git a/backend/config/database.php b/backend/config/database.php old mode 100644 new mode 100755 diff --git a/backend/config/fib.php b/backend/config/fib.php old mode 100644 new mode 100755 diff --git a/backend/config/filesystems.php b/backend/config/filesystems.php old mode 100644 new mode 100755 diff --git a/backend/config/logging.php b/backend/config/logging.php old mode 100644 new mode 100755 diff --git a/backend/config/mail.php b/backend/config/mail.php old mode 100644 new mode 100755 diff --git a/backend/config/passport.php b/backend/config/passport.php old mode 100644 new mode 100755 diff --git a/backend/config/permission.php b/backend/config/permission.php old mode 100644 new mode 100755 diff --git a/backend/config/queue.php b/backend/config/queue.php old mode 100644 new mode 100755 diff --git a/backend/config/services.php b/backend/config/services.php old mode 100644 new mode 100755 diff --git a/backend/database/.gitignore b/backend/database/.gitignore old mode 100644 new mode 100755 diff --git a/backend/database/factories/CityFactory.php b/backend/database/factories/CityFactory.php old mode 100644 new mode 100755 diff --git a/backend/database/factories/DocumentFactory.php b/backend/database/factories/DocumentFactory.php old mode 100644 new mode 100755 diff --git a/backend/database/factories/ExpertFactory.php b/backend/database/factories/ExpertFactory.php old mode 100644 new mode 100755 diff --git a/backend/database/factories/ProvinceFactory.php b/backend/database/factories/ProvinceFactory.php old mode 100644 new mode 100755 diff --git a/backend/database/migrations/0001_01_00_000000_create_provinces_table.php b/backend/database/migrations/0001_01_00_000000_create_provinces_table.php old mode 100644 new mode 100755 diff --git a/backend/database/migrations/0001_01_00_000001_create_cities_table.php b/backend/database/migrations/0001_01_00_000001_create_cities_table.php old mode 100644 new mode 100755 diff --git a/backend/database/migrations/0001_01_01_000001_create_cache_table.php b/backend/database/migrations/0001_01_01_000001_create_cache_table.php old mode 100644 new mode 100755 diff --git a/backend/database/migrations/0001_01_01_000002_create_jobs_table.php b/backend/database/migrations/0001_01_01_000002_create_jobs_table.php old mode 100644 new mode 100755 diff --git a/backend/database/migrations/2026_04_27_122415_create_oauth_auth_codes_table.php b/backend/database/migrations/2026_04_27_122415_create_oauth_auth_codes_table.php old mode 100644 new mode 100755 diff --git a/backend/database/migrations/2026_04_27_122416_create_oauth_access_tokens_table.php b/backend/database/migrations/2026_04_27_122416_create_oauth_access_tokens_table.php old mode 100644 new mode 100755 diff --git a/backend/database/migrations/2026_04_27_122417_create_oauth_refresh_tokens_table.php b/backend/database/migrations/2026_04_27_122417_create_oauth_refresh_tokens_table.php old mode 100644 new mode 100755 diff --git a/backend/database/migrations/2026_04_27_122418_create_oauth_clients_table.php b/backend/database/migrations/2026_04_27_122418_create_oauth_clients_table.php old mode 100644 new mode 100755 diff --git a/backend/database/migrations/2026_04_27_122419_create_oauth_device_codes_table.php b/backend/database/migrations/2026_04_27_122419_create_oauth_device_codes_table.php old mode 100644 new mode 100755 diff --git a/backend/database/migrations/2026_05_10_123913_create_permission_tables.php b/backend/database/migrations/2026_05_10_123913_create_permission_tables.php old mode 100644 new mode 100755 diff --git a/backend/database/migrations/2026_05_12_053258_create_documents_table.php b/backend/database/migrations/2026_05_12_053258_create_documents_table.php old mode 100644 new mode 100755 diff --git a/backend/database/migrations/2026_05_12_074230_create_experts_table.php b/backend/database/migrations/2026_05_12_074230_create_experts_table.php old mode 100644 new mode 100755 diff --git a/backend/database/migrations/2026_05_13_054404_add_column_to_users_table.php b/backend/database/migrations/2026_05_13_054404_add_column_to_users_table.php old mode 100644 new mode 100755 diff --git a/backend/database/seeders/CitySeeder.php b/backend/database/seeders/CitySeeder.php old mode 100644 new mode 100755 diff --git a/backend/database/seeders/DatabaseSeeder.php b/backend/database/seeders/DatabaseSeeder.php index 55af5ff8..90d0b1b1 100755 --- a/backend/database/seeders/DatabaseSeeder.php +++ b/backend/database/seeders/DatabaseSeeder.php @@ -18,7 +18,7 @@ class DatabaseSeeder extends Seeder // User::factory(10)->create(); User::factory()->create([ - 'username' => 'Test User', + 'username' => 'test', 'email' => 'test@example.com', 'password' => 'password123@', ]); diff --git a/backend/database/seeders/DocumentSeeder.php b/backend/database/seeders/DocumentSeeder.php old mode 100644 new mode 100755 diff --git a/backend/database/seeders/ExpertSeeder.php b/backend/database/seeders/ExpertSeeder.php old mode 100644 new mode 100755 diff --git a/backend/database/seeders/ProvinceSeeder.php b/backend/database/seeders/ProvinceSeeder.php old mode 100644 new mode 100755 diff --git a/backend/package.json b/backend/package.json old mode 100644 new mode 100755 diff --git a/backend/phpunit.xml b/backend/phpunit.xml old mode 100644 new mode 100755 diff --git a/backend/public/.htaccess b/backend/public/.htaccess old mode 100644 new mode 100755 diff --git a/backend/public/favicon.ico b/backend/public/favicon.ico old mode 100644 new mode 100755 diff --git a/backend/public/index.php b/backend/public/index.php old mode 100644 new mode 100755 diff --git a/backend/public/robots.txt b/backend/public/robots.txt old mode 100644 new mode 100755 diff --git a/backend/resources/css/app.css b/backend/resources/css/app.css old mode 100644 new mode 100755 diff --git a/backend/resources/js/app.js b/backend/resources/js/app.js old mode 100644 new mode 100755 diff --git a/backend/resources/lang/ar/notifications.php b/backend/resources/lang/ar/notifications.php old mode 100644 new mode 100755 diff --git a/backend/resources/lang/en/auth.php b/backend/resources/lang/en/auth.php old mode 100644 new mode 100755 diff --git a/backend/resources/lang/en/messages.php b/backend/resources/lang/en/messages.php old mode 100644 new mode 100755 diff --git a/backend/resources/lang/en/pagination.php b/backend/resources/lang/en/pagination.php old mode 100644 new mode 100755 diff --git a/backend/resources/lang/en/passwords.php b/backend/resources/lang/en/passwords.php old mode 100644 new mode 100755 diff --git a/backend/resources/lang/en/validation.php b/backend/resources/lang/en/validation.php old mode 100644 new mode 100755 diff --git a/backend/resources/lang/fa/auth.php b/backend/resources/lang/fa/auth.php old mode 100644 new mode 100755 diff --git a/backend/resources/lang/fa/messages.php b/backend/resources/lang/fa/messages.php old mode 100644 new mode 100755 diff --git a/backend/resources/lang/fa/pagination.php b/backend/resources/lang/fa/pagination.php old mode 100644 new mode 100755 diff --git a/backend/resources/lang/fa/passwords.php b/backend/resources/lang/fa/passwords.php old mode 100644 new mode 100755 diff --git a/backend/resources/lang/fa/validation.php b/backend/resources/lang/fa/validation.php old mode 100644 new mode 100755 diff --git a/backend/resources/views/emails/login-notification.blade.php b/backend/resources/views/emails/login-notification.blade.php old mode 100644 new mode 100755 diff --git a/backend/resources/views/welcome.blade.php b/backend/resources/views/welcome.blade.php old mode 100644 new mode 100755 diff --git a/backend/routes/console.php b/backend/routes/console.php old mode 100644 new mode 100755 diff --git a/backend/routes/expert.php b/backend/routes/expert.php index db005e64..50400564 100755 --- a/backend/routes/expert.php +++ b/backend/routes/expert.php @@ -2,6 +2,10 @@ use App\Admin\Controllers\AuthController; use App\Admin\Controllers\DocumentationController; +use App\Admin\Controllers\ExpertManagementController; +use App\Admin\Controllers\PermissionManagementController; +use App\Admin\Controllers\RoleManagementController; +use App\Admin\Controllers\UserManagementController; Route::prefix('auth') ->name('auth.') @@ -18,7 +22,52 @@ Route::prefix('documents') ->controller(DocumentationController::class) ->group(function () { Route::get('/', 'index')->name('index'); - Route::post('confirm', 'confirm')->name('confirm'); - Route::post('reject', 'reject')->name('reject'); + Route::post('confirm/{user}', 'confirm')->name('confirm'); + Route::post('reject/{user}', 'reject')->name('reject'); Route::get('files/{user}', 'files')->name('files'); }); + +Route::prefix('user_management') + ->middleware(['auth:expert', 'permission:user-management']) + ->name('user_management.') + ->controller(UserManagementController::class) + ->group(function () { + Route::get('/', 'index')->name('index'); + Route::post('/', 'store')->name('store'); + Route::get('/{user}', 'show')->name('show'); + Route::post('/{user}', 'update')->name('update'); + Route::delete('/{user}', 'destroy')->name('destroy'); + }); +Route::prefix('roles') + ->name('roles.') + ->middleware(['auth:expert', 'permission:role-management']) + ->controller(RoleManagementController::class) + ->group(function () { + Route::get('/', 'index')->name('index'); + Route::post('/', 'store')->name('store'); + Route::get('/{role}', 'show')->name('show'); + Route::post('/{role}', 'update')->name('update'); + Route::delete('/{role}', 'destroy')->name('destroy'); + }); +Route::prefix('permissions') + ->name('permissions.') + ->middleware(['auth:expert', 'permission:permission-management']) + ->controller(PermissionManagementController::class) + ->group(function () { + Route::get('/', 'index')->name('index'); + Route::post('/', 'store')->name('store'); + Route::get('/{permission}', 'show')->name('show'); + Route::post('/{permission}', 'update')->name('update'); + Route::delete('/{permission}', 'destroy')->name('destroy'); + }); +Route::prefix('expert') + ->name('expert.') + ->middleware(['auth:expert', 'permission:expert-management']) + ->controller(ExpertManagementController::class) + ->group(function () { + Route::get('/', 'index')->name('index'); + Route::post('/', 'store')->name('store'); + Route::get('/{expert}', 'show')->name('show'); + Route::post('/{expert}', 'update')->name('update'); + Route::delete('/{expert}', 'destroy')->name('destroy'); + }); diff --git a/backend/routes/web.php b/backend/routes/web.php index b81845c1..28784019 100755 --- a/backend/routes/web.php +++ b/backend/routes/web.php @@ -26,49 +26,3 @@ Route::prefix('profile') Route::post('edit', 'edit')->name('edit'); Route::post('change_password', 'changePassword')->name('changePassword'); }); - -Route::prefix('admin') - ->name('Admin.') - ->group(function () { - Route::prefix('user_management') - ->name('user_management.') - ->controller(UserManagementController::class) - ->group(function () { - Route::get('/', 'index')->name('index'); - Route::post('/', 'store')->name('store'); - Route::get('/{user}', 'show')->name('show'); - Route::post('/{user}', 'update')->name('update'); - Route::delete('/{user}', 'destroy')->name('destroy'); - }); - Route::prefix('roles') - ->name('roles.') - ->controller(RoleManagementController::class) - ->group(function () { - Route::get('/', 'index')->name('index'); - Route::post('/', 'store')->name('store'); - Route::get('/{role}', 'show')->name('show'); - Route::post('/{role}', 'update')->name('update'); - Route::delete('/{role}', 'destroy')->name('destroy'); - }); - Route::prefix('permissions') - ->name('permissions.') - ->controller(PermissionManagementController::class) - ->group(function () { - Route::get('/', 'index')->name('index'); - Route::post('/', 'store')->name('store'); - Route::get('/{permission}', 'show')->name('show'); - Route::post('/{permission}', 'update')->name('update'); - Route::delete('/{permission}', 'destroy')->name('destroy'); - }); - Route::prefix('expert') - ->name('expert.') - ->controller(ExpertManagementController::class) - ->group(function () { - Route::get('/', 'index')->name('index'); - Route::post('/', 'store')->name('store'); - Route::get('/{expert}', 'show')->name('show'); - Route::post('/{expert}', 'update')->name('update'); - Route::delete('/{expert}', 'destroy')->name('destroy'); - }); - }); - diff --git a/backend/tests/Feature/ExampleTest.php b/backend/tests/Feature/ExampleTest.php old mode 100644 new mode 100755 diff --git a/backend/tests/Pest.php b/backend/tests/Pest.php old mode 100644 new mode 100755 diff --git a/backend/tests/TestCase.php b/backend/tests/TestCase.php old mode 100644 new mode 100755 diff --git a/backend/tests/Unit/ExampleTest.php b/backend/tests/Unit/ExampleTest.php old mode 100644 new mode 100755 diff --git a/backend/vite.config.js b/backend/vite.config.js old mode 100644 new mode 100755 -- 2.49.1 From e53a89016f3130d2e60504e27017e2da7eb22d6a Mon Sep 17 00:00:00 2001 From: amirghasempoor Date: Sun, 17 May 2026 13:57:39 +0330 Subject: [PATCH 37/61] create payment and gateway resources --- backend/app/Models/Gateway.php | 15 ++++++++ backend/app/Models/GatewayCategory.php | 12 ++++++ backend/app/Models/Payment.php | 15 ++++++++ backend/app/Models/PaymentStatus.php | 12 ++++++ .../Services/FIB/AuthorizationService.php | 2 +- .../Services/FIB/CancelPaymentService.php | 2 +- .../FIB/CheckPaymentStatusService.php | 2 +- .../Services/FIB/CreatePaymentService.php | 2 +- .../{User => }/Services/FIB/RefundService.php | 2 +- .../factories/GatewayCategoryFactory.php | 24 ++++++++++++ backend/database/factories/GatewayFactory.php | 24 ++++++++++++ backend/database/factories/PaymentFactory.php | 24 ++++++++++++ .../factories/PaymentStatusFactory.php | 24 ++++++++++++ ...7_053830_create_payment_statuses_table.php | 28 ++++++++++++++ ...053831_create_gateway_categories_table.php | 28 ++++++++++++++ ...026_05_17_053832_create_gateways_table.php | 38 +++++++++++++++++++ ...026_05_17_053833_create_payments_table.php | 38 +++++++++++++++++++ .../seeders/GatewayCategorySeeder.php | 17 +++++++++ backend/database/seeders/GatewaySeeder.php | 17 +++++++++ backend/database/seeders/PaymentSeeder.php | 17 +++++++++ .../database/seeders/PaymentStatusSeeder.php | 17 +++++++++ 21 files changed, 355 insertions(+), 5 deletions(-) create mode 100644 backend/app/Models/Gateway.php create mode 100644 backend/app/Models/GatewayCategory.php create mode 100644 backend/app/Models/Payment.php create mode 100644 backend/app/Models/PaymentStatus.php rename backend/app/{User => }/Services/FIB/AuthorizationService.php (97%) rename backend/app/{User => }/Services/FIB/CancelPaymentService.php (97%) rename backend/app/{User => }/Services/FIB/CheckPaymentStatusService.php (96%) rename backend/app/{User => }/Services/FIB/CreatePaymentService.php (98%) rename backend/app/{User => }/Services/FIB/RefundService.php (97%) create mode 100644 backend/database/factories/GatewayCategoryFactory.php create mode 100644 backend/database/factories/GatewayFactory.php create mode 100644 backend/database/factories/PaymentFactory.php create mode 100644 backend/database/factories/PaymentStatusFactory.php create mode 100644 backend/database/migrations/2026_05_17_053830_create_payment_statuses_table.php create mode 100644 backend/database/migrations/2026_05_17_053831_create_gateway_categories_table.php create mode 100644 backend/database/migrations/2026_05_17_053832_create_gateways_table.php create mode 100644 backend/database/migrations/2026_05_17_053833_create_payments_table.php create mode 100644 backend/database/seeders/GatewayCategorySeeder.php create mode 100644 backend/database/seeders/GatewaySeeder.php create mode 100644 backend/database/seeders/PaymentSeeder.php create mode 100644 backend/database/seeders/PaymentStatusSeeder.php diff --git a/backend/app/Models/Gateway.php b/backend/app/Models/Gateway.php new file mode 100644 index 00000000..d7b58f9c --- /dev/null +++ b/backend/app/Models/Gateway.php @@ -0,0 +1,15 @@ + */ + use HasFactory; + + protected $guarded = ['id']; +} diff --git a/backend/app/Models/GatewayCategory.php b/backend/app/Models/GatewayCategory.php new file mode 100644 index 00000000..a6e44afe --- /dev/null +++ b/backend/app/Models/GatewayCategory.php @@ -0,0 +1,12 @@ + */ + use HasFactory; +} diff --git a/backend/app/Models/Payment.php b/backend/app/Models/Payment.php new file mode 100644 index 00000000..f4d1f85a --- /dev/null +++ b/backend/app/Models/Payment.php @@ -0,0 +1,15 @@ + */ + use HasFactory; + + protected $guarded = ['id']; +} diff --git a/backend/app/Models/PaymentStatus.php b/backend/app/Models/PaymentStatus.php new file mode 100644 index 00000000..c222ee0e --- /dev/null +++ b/backend/app/Models/PaymentStatus.php @@ -0,0 +1,12 @@ + */ + use HasFactory; +} diff --git a/backend/app/User/Services/FIB/AuthorizationService.php b/backend/app/Services/FIB/AuthorizationService.php similarity index 97% rename from backend/app/User/Services/FIB/AuthorizationService.php rename to backend/app/Services/FIB/AuthorizationService.php index 9e67d55d..9867133f 100755 --- a/backend/app/User/Services/FIB/AuthorizationService.php +++ b/backend/app/Services/FIB/AuthorizationService.php @@ -1,6 +1,6 @@ + */ +class GatewayCategoryFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + // + ]; + } +} diff --git a/backend/database/factories/GatewayFactory.php b/backend/database/factories/GatewayFactory.php new file mode 100644 index 00000000..f8029ab5 --- /dev/null +++ b/backend/database/factories/GatewayFactory.php @@ -0,0 +1,24 @@ + + */ +class GatewayFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + // + ]; + } +} diff --git a/backend/database/factories/PaymentFactory.php b/backend/database/factories/PaymentFactory.php new file mode 100644 index 00000000..8bfd4911 --- /dev/null +++ b/backend/database/factories/PaymentFactory.php @@ -0,0 +1,24 @@ + + */ +class PaymentFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + // + ]; + } +} diff --git a/backend/database/factories/PaymentStatusFactory.php b/backend/database/factories/PaymentStatusFactory.php new file mode 100644 index 00000000..390010db --- /dev/null +++ b/backend/database/factories/PaymentStatusFactory.php @@ -0,0 +1,24 @@ + + */ +class PaymentStatusFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + // + ]; + } +} diff --git a/backend/database/migrations/2026_05_17_053830_create_payment_statuses_table.php b/backend/database/migrations/2026_05_17_053830_create_payment_statuses_table.php new file mode 100644 index 00000000..754a8790 --- /dev/null +++ b/backend/database/migrations/2026_05_17_053830_create_payment_statuses_table.php @@ -0,0 +1,28 @@ +id(); + $table->string('name'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('payment_statuses'); + } +}; diff --git a/backend/database/migrations/2026_05_17_053831_create_gateway_categories_table.php b/backend/database/migrations/2026_05_17_053831_create_gateway_categories_table.php new file mode 100644 index 00000000..16ee53ea --- /dev/null +++ b/backend/database/migrations/2026_05_17_053831_create_gateway_categories_table.php @@ -0,0 +1,28 @@ +id(); + $table->string('name'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('gateway_categories'); + } +}; diff --git a/backend/database/migrations/2026_05_17_053832_create_gateways_table.php b/backend/database/migrations/2026_05_17_053832_create_gateways_table.php new file mode 100644 index 00000000..faab5d50 --- /dev/null +++ b/backend/database/migrations/2026_05_17_053832_create_gateways_table.php @@ -0,0 +1,38 @@ +id(); + $table->foreignId('user_id')->constrained('users'); + $table->string('username'); + $table->string('name'); + $table->string('domain'); + $table->string('tax_id'); + $table->string('bank_name'); + $table->string('account_holder'); + $table->string('logo')->nullable(); + $table->string('iban'); + $table->foreignId('category_id')->constrained('gateway_categories'); + $table->string('category_name'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('gateways'); + } +}; diff --git a/backend/database/migrations/2026_05_17_053833_create_payments_table.php b/backend/database/migrations/2026_05_17_053833_create_payments_table.php new file mode 100644 index 00000000..0bf5cc2e --- /dev/null +++ b/backend/database/migrations/2026_05_17_053833_create_payments_table.php @@ -0,0 +1,38 @@ +id(); + $table->foreignId('user_id')->constrained('users'); + $table->string('username'); + $table->uuid('track_id'); + $table->string('amount'); + $table->string('currency'); + $table->string('callback_url'); + $table->dateTime('expires_at'); + $table->foreignId('status_id')->constrained('payment_statuses'); + $table->string('status_name'); + $table->ipAddress('ip'); + $table->foreignId('gateway_id')->constrained('gateways'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('payments'); + } +}; diff --git a/backend/database/seeders/GatewayCategorySeeder.php b/backend/database/seeders/GatewayCategorySeeder.php new file mode 100644 index 00000000..6e41f3b8 --- /dev/null +++ b/backend/database/seeders/GatewayCategorySeeder.php @@ -0,0 +1,17 @@ + Date: Sun, 17 May 2026 14:00:33 +0330 Subject: [PATCH 38/61] add gaurded values --- backend/app/Models/GatewayCategory.php | 5 ++++- backend/app/Models/PaymentStatus.php | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/backend/app/Models/GatewayCategory.php b/backend/app/Models/GatewayCategory.php index a6e44afe..dd1e29df 100644 --- a/backend/app/Models/GatewayCategory.php +++ b/backend/app/Models/GatewayCategory.php @@ -2,11 +2,14 @@ namespace App\Models; +use Database\Factories\GatewayCategoryFactory; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class GatewayCategory extends Model { - /** @use HasFactory<\Database\Factories\GatewayCategoryFactory> */ + /** @use HasFactory */ use HasFactory; + + protected $guarded = ['id']; } diff --git a/backend/app/Models/PaymentStatus.php b/backend/app/Models/PaymentStatus.php index c222ee0e..b173c343 100644 --- a/backend/app/Models/PaymentStatus.php +++ b/backend/app/Models/PaymentStatus.php @@ -2,11 +2,14 @@ namespace App\Models; +use Database\Factories\PaymentStatusFactory; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class PaymentStatus extends Model { - /** @use HasFactory<\Database\Factories\PaymentStatusFactory> */ + /** @use HasFactory */ use HasFactory; + + protected $guarded = ['id']; } -- 2.49.1 From 86e353749a0fd10b02288ade45533d74b54015e0 Mon Sep 17 00:00:00 2001 From: faezehzafarbakhsh Date: Mon, 18 May 2026 13:49:33 +0330 Subject: [PATCH 39/61] create profile for expert --- .../Admin/Controllers/ProfileController.php | 20 +++++ .../Admin/Requests/Profile/EditRequest.php | 78 +++++++++++++++++++ .../User/Controllers/ProfileController.php | 18 ++--- .../app/User/Requests/Profile/EditRequest.php | 42 +++++++++- .../database/seeders/PaymentStatusSeeder.php | 22 +++++- backend/routes/expert.php | 11 +++ 6 files changed, 176 insertions(+), 15 deletions(-) create mode 100755 backend/app/Admin/Requests/Profile/EditRequest.php diff --git a/backend/app/Admin/Controllers/ProfileController.php b/backend/app/Admin/Controllers/ProfileController.php index 5b825966..dde39f98 100755 --- a/backend/app/Admin/Controllers/ProfileController.php +++ b/backend/app/Admin/Controllers/ProfileController.php @@ -3,6 +3,7 @@ namespace App\Admin\Controllers; use App\Admin\Requests\Profile\ChangePasswordRequest; +use App\Admin\Requests\Profile\EditRequest; use App\Admin\Resources\ExpertResource; use App\Http\Controllers\Controller; use App\Traits\ApiResponse; @@ -19,6 +20,25 @@ class ProfileController extends Controller return $this->successResponse(new ExpertResource(Auth::guard('expert')->user())); } + public function edit(EditRequest $request): JsonResponse + { + $expert = $request->user('expert'); +// $expert->update($request->validated()); + + $expert->update([ + 'username' => $request->username, + 'first_name' => $request->first_name, + 'last_name' => $request->last_name, + 'email' => $expert->email, + 'national_id' => $request->national_id, + 'phone_number' => $request->phone_number, + 'province_id' => $request->province_id, + 'city_id' => $request->city_id, + ]); + + return $this->successResponse(); + } + public function changePassword(ChangePasswordRequest $request): JsonResponse { $expert = Auth::guard('expert')->user(); diff --git a/backend/app/Admin/Requests/Profile/EditRequest.php b/backend/app/Admin/Requests/Profile/EditRequest.php new file mode 100755 index 00000000..2e56b73f --- /dev/null +++ b/backend/app/Admin/Requests/Profile/EditRequest.php @@ -0,0 +1,78 @@ + + */ + public function rules(): array + { + $user = $this->user(); + return [ + 'first_name' => [ + Rule::prohibitedIf($user->kyc_status == 1), 'string', + ], + + 'last_name' => [ + Rule::prohibitedIf($user->kyc_status == 1), 'string', + ], + + 'national_id' => [ + Rule::prohibitedIf($user->kyc_status == 1), 'string', + ], + 'username' => 'string|max:255', + + 'email' => 'string|email|max:255', + 'phone_number' => 'string', + 'province_id' => 'string', + 'city_id' => 'string', + ]; + } + + public function after(): array + { + return [ + function (Validator $validator) { + $user = $this->user(); + + if (! $user) { + return; + } + + if ($user->kyc_status == 1) { + $lockedFields = [ + 'first_name', + 'last_name', + 'national_id', + ]; + + foreach ($lockedFields as $field) { + if ($this->filled($field)) { + $validator->errors()->add( + $field, + 'After KYC approval, you cannot change this field.' + ); + } + } + } + }, + ]; + } +} diff --git a/backend/app/User/Controllers/ProfileController.php b/backend/app/User/Controllers/ProfileController.php index 78f583c9..9afaa04a 100755 --- a/backend/app/User/Controllers/ProfileController.php +++ b/backend/app/User/Controllers/ProfileController.php @@ -24,17 +24,13 @@ class ProfileController extends Controller { $user = Auth::user(); - $user->update([ - 'first_name' => $request->first_name, - 'last_name' => $request->last_name, - 'national_id' => $request->national_id, - 'address' => $request->address, - 'postal_code' => $request->postal_code, - 'phone_number' => $request->phone_number, - 'province_id' => $request->province_id, - 'city_id' => $request->city_id, - 'status' => 1 - ]); + $data = $request->validated(); + + if ($user->kyc_status === 2) { + $data->safe()->except(['first_name', 'last_name' ,'national_id']); + } + + $user->update($data); return $this->successResponse(); } diff --git a/backend/app/User/Requests/Profile/EditRequest.php b/backend/app/User/Requests/Profile/EditRequest.php index 4eb93a4e..4b0e1eb4 100755 --- a/backend/app/User/Requests/Profile/EditRequest.php +++ b/backend/app/User/Requests/Profile/EditRequest.php @@ -4,6 +4,8 @@ namespace App\User\Requests\Profile; use Illuminate\Contracts\Validation\ValidationRule; use Illuminate\Foundation\Http\FormRequest; +use Illuminate\Validation\Rule; +use Illuminate\Validation\Validator; class EditRequest extends FormRequest { @@ -22,10 +24,32 @@ class EditRequest extends FormRequest */ public function rules(): array { + $user = $this->user(); + $isKycApproved = (int) $user->kyc_status === 2; + return [ - 'first_name' => 'required|string', - 'last_name' => 'required|string', - 'national_id' => 'required', + 'first_name' => [ + Rule::prohibitedIf($isKycApproved), + 'required', + 'string', + 'max:255', + ], + + 'last_name' => [ + Rule::prohibitedIf($isKycApproved), + 'required', + 'string', + 'max:255', + ], + + 'national_id' => [ + Rule::prohibitedIf($isKycApproved), + 'required', + 'string', + 'max:20', + Rule::unique('users', 'national_id')->ignore($user->id), + ], + 'address' => 'required', 'postal_code' => 'required', 'phone_number' => 'required', @@ -33,4 +57,16 @@ class EditRequest extends FormRequest 'city_id' => 'required', ]; } + public function after(): array + { + return [ + function (Validator $validator) { + $user = $this->user(); + + if ( $user->kyc_status === 1) { + if + } + }, + ]; + } } diff --git a/backend/database/seeders/PaymentStatusSeeder.php b/backend/database/seeders/PaymentStatusSeeder.php index 5550baae..89ece425 100644 --- a/backend/database/seeders/PaymentStatusSeeder.php +++ b/backend/database/seeders/PaymentStatusSeeder.php @@ -4,6 +4,7 @@ namespace Database\Seeders; use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; +use Illuminate\Support\Facades\DB; class PaymentStatusSeeder extends Seeder { @@ -12,6 +13,25 @@ class PaymentStatusSeeder extends Seeder */ public function run(): void { - // + DB::table('harim_states')->insert([ + ['id' => 1, 'name' => '', 'created_at' => now(), 'updated_at' => now(),], + ['id' => 2, 'name' => '', 'created_at' => now(), 'updated_at' => now(),], + ['id' => 3, 'name' => '', 'created_at' => now(), 'updated_at' => now(),], + ['id' => 4, 'name' => '', 'created_at' => now(), 'updated_at' => now(),], + ['id' => 5, 'name' => '', 'created_at' => now(), 'updated_at' => now(),], + ['id' => 6, 'name' => '', 'created_at' => now(), 'updated_at' => now(),], + ['id' => 7, 'name' => '', 'created_at' => now(), 'updated_at' => now(),], + ['id' => 8, 'name' => '', 'created_at' => now(), 'updated_at' => now(),], + ['id' => 9, 'name' => '', 'created_at' => now(), 'updated_at' => now(),], + ['id' => 10, 'name' => '', 'created_at' => now(), 'updated_at' => now(),], + ['id' => 11, 'name' => '', 'created_at' => now(), 'updated_at' => now(),], + ['id' => 12, 'name' => '', 'created_at' => now(), 'updated_at' => now(),], + ['id' => 13, 'name' => '', 'created_at' => now(), 'updated_at' => now(),], + ['id' => 14, 'name' => '', 'created_at' => now(), 'updated_at' => now(),], + ['id' => 15, 'name' => '', 'created_at' => now(), 'updated_at' => now(),], + ['id' => 16, 'name' => '', 'created_at' => now(), 'updated_at' => now(),], + ['id' => 17, 'name' => '', 'created_at' => now(), 'updated_at' => now(),], + ]); + } } diff --git a/backend/routes/expert.php b/backend/routes/expert.php index 50400564..a40d9dcd 100755 --- a/backend/routes/expert.php +++ b/backend/routes/expert.php @@ -4,6 +4,7 @@ use App\Admin\Controllers\AuthController; use App\Admin\Controllers\DocumentationController; use App\Admin\Controllers\ExpertManagementController; use App\Admin\Controllers\PermissionManagementController; +use App\Admin\Controllers\ProfileController; use App\Admin\Controllers\RoleManagementController; use App\Admin\Controllers\UserManagementController; @@ -71,3 +72,13 @@ Route::prefix('expert') Route::post('/{expert}', 'update')->name('update'); Route::delete('/{expert}', 'destroy')->name('destroy'); }); + +Route::prefix('profile') + ->name('profile.') + ->middleware('auth') + ->controller(ProfileController::class) + ->group(function () { + Route::get('info', 'info')->name('info'); + Route::post('edit', 'edit')->name('edit'); + Route::post('change_password', 'changePassword')->name('changePassword'); + }); -- 2.49.1 From 2d8f2dbcc5abaa12306e641d459e3dca847a7d14 Mon Sep 17 00:00:00 2001 From: faezehzafarbakhsh Date: Mon, 18 May 2026 13:50:37 +0330 Subject: [PATCH 40/61] create profile for expert --- .../database/seeders/PaymentStatusSeeder.php | 21 +------------------ 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/backend/database/seeders/PaymentStatusSeeder.php b/backend/database/seeders/PaymentStatusSeeder.php index 89ece425..3232c3c7 100644 --- a/backend/database/seeders/PaymentStatusSeeder.php +++ b/backend/database/seeders/PaymentStatusSeeder.php @@ -13,25 +13,6 @@ class PaymentStatusSeeder extends Seeder */ public function run(): void { - DB::table('harim_states')->insert([ - ['id' => 1, 'name' => '', 'created_at' => now(), 'updated_at' => now(),], - ['id' => 2, 'name' => '', 'created_at' => now(), 'updated_at' => now(),], - ['id' => 3, 'name' => '', 'created_at' => now(), 'updated_at' => now(),], - ['id' => 4, 'name' => '', 'created_at' => now(), 'updated_at' => now(),], - ['id' => 5, 'name' => '', 'created_at' => now(), 'updated_at' => now(),], - ['id' => 6, 'name' => '', 'created_at' => now(), 'updated_at' => now(),], - ['id' => 7, 'name' => '', 'created_at' => now(), 'updated_at' => now(),], - ['id' => 8, 'name' => '', 'created_at' => now(), 'updated_at' => now(),], - ['id' => 9, 'name' => '', 'created_at' => now(), 'updated_at' => now(),], - ['id' => 10, 'name' => '', 'created_at' => now(), 'updated_at' => now(),], - ['id' => 11, 'name' => '', 'created_at' => now(), 'updated_at' => now(),], - ['id' => 12, 'name' => '', 'created_at' => now(), 'updated_at' => now(),], - ['id' => 13, 'name' => '', 'created_at' => now(), 'updated_at' => now(),], - ['id' => 14, 'name' => '', 'created_at' => now(), 'updated_at' => now(),], - ['id' => 15, 'name' => '', 'created_at' => now(), 'updated_at' => now(),], - ['id' => 16, 'name' => '', 'created_at' => now(), 'updated_at' => now(),], - ['id' => 17, 'name' => '', 'created_at' => now(), 'updated_at' => now(),], - ]); - + // } } -- 2.49.1 From 60129f5abba112310889320bb43064d972a94863 Mon Sep 17 00:00:00 2001 From: faezehzafarbakhsh Date: Mon, 18 May 2026 13:57:03 +0330 Subject: [PATCH 41/61] create profile for expert --- backend/database/seeders/PaymentStatusSeeder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/database/seeders/PaymentStatusSeeder.php b/backend/database/seeders/PaymentStatusSeeder.php index 3232c3c7..4805963c 100644 --- a/backend/database/seeders/PaymentStatusSeeder.php +++ b/backend/database/seeders/PaymentStatusSeeder.php @@ -13,6 +13,6 @@ class PaymentStatusSeeder extends Seeder */ public function run(): void { - // + // } } -- 2.49.1 From e9f6ee71faa901023693e1ccfa26c54d8d250d47 Mon Sep 17 00:00:00 2001 From: faezehzafarbakhsh Date: Mon, 18 May 2026 13:58:20 +0330 Subject: [PATCH 42/61] create profile for expert --- backend/database/seeders/PaymentStatusSeeder.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/backend/database/seeders/PaymentStatusSeeder.php b/backend/database/seeders/PaymentStatusSeeder.php index 4805963c..adaa9b47 100644 --- a/backend/database/seeders/PaymentStatusSeeder.php +++ b/backend/database/seeders/PaymentStatusSeeder.php @@ -4,8 +4,6 @@ namespace Database\Seeders; use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; -use Illuminate\Support\Facades\DB; - class PaymentStatusSeeder extends Seeder { /** -- 2.49.1 From 3c61e279ec2135523234fa447a728c9b54ea5372 Mon Sep 17 00:00:00 2001 From: faezehzafarbakhsh Date: Mon, 18 May 2026 13:59:05 +0330 Subject: [PATCH 43/61] create profile for expert --- backend/database/seeders/PaymentStatusSeeder.php | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/database/seeders/PaymentStatusSeeder.php b/backend/database/seeders/PaymentStatusSeeder.php index adaa9b47..5550baae 100644 --- a/backend/database/seeders/PaymentStatusSeeder.php +++ b/backend/database/seeders/PaymentStatusSeeder.php @@ -4,6 +4,7 @@ namespace Database\Seeders; use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; + class PaymentStatusSeeder extends Seeder { /** -- 2.49.1 From d04637b1efea7a4e1b4eb95e82007c052d62a071 Mon Sep 17 00:00:00 2001 From: faezehzafarbakhsh Date: Mon, 18 May 2026 14:10:45 +0330 Subject: [PATCH 44/61] fix code --- .../Admin/Controllers/ProfileController.php | 1 - .../Admin/Requests/Profile/EditRequest.php | 45 ++----------------- .../User/Controllers/ProfileController.php | 8 +--- .../app/User/Requests/Profile/EditRequest.php | 38 ++++++++-------- 4 files changed, 22 insertions(+), 70 deletions(-) diff --git a/backend/app/Admin/Controllers/ProfileController.php b/backend/app/Admin/Controllers/ProfileController.php index dde39f98..23a80f7e 100755 --- a/backend/app/Admin/Controllers/ProfileController.php +++ b/backend/app/Admin/Controllers/ProfileController.php @@ -23,7 +23,6 @@ class ProfileController extends Controller public function edit(EditRequest $request): JsonResponse { $expert = $request->user('expert'); -// $expert->update($request->validated()); $expert->update([ 'username' => $request->username, diff --git a/backend/app/Admin/Requests/Profile/EditRequest.php b/backend/app/Admin/Requests/Profile/EditRequest.php index 2e56b73f..ccc056b4 100755 --- a/backend/app/Admin/Requests/Profile/EditRequest.php +++ b/backend/app/Admin/Requests/Profile/EditRequest.php @@ -26,53 +26,14 @@ class EditRequest extends FormRequest { $user = $this->user(); return [ - 'first_name' => [ - Rule::prohibitedIf($user->kyc_status == 1), 'string', - ], - - 'last_name' => [ - Rule::prohibitedIf($user->kyc_status == 1), 'string', - ], - - 'national_id' => [ - Rule::prohibitedIf($user->kyc_status == 1), 'string', - ], 'username' => 'string|max:255', - + 'first_name' => 'required|string', + 'last_name' => 'required|string', + 'national_id' => 'required', 'email' => 'string|email|max:255', 'phone_number' => 'string', 'province_id' => 'string', 'city_id' => 'string', ]; } - - public function after(): array - { - return [ - function (Validator $validator) { - $user = $this->user(); - - if (! $user) { - return; - } - - if ($user->kyc_status == 1) { - $lockedFields = [ - 'first_name', - 'last_name', - 'national_id', - ]; - - foreach ($lockedFields as $field) { - if ($this->filled($field)) { - $validator->errors()->add( - $field, - 'After KYC approval, you cannot change this field.' - ); - } - } - } - }, - ]; - } } diff --git a/backend/app/User/Controllers/ProfileController.php b/backend/app/User/Controllers/ProfileController.php index 9afaa04a..3d89e5dc 100755 --- a/backend/app/User/Controllers/ProfileController.php +++ b/backend/app/User/Controllers/ProfileController.php @@ -24,13 +24,7 @@ class ProfileController extends Controller { $user = Auth::user(); - $data = $request->validated(); - - if ($user->kyc_status === 2) { - $data->safe()->except(['first_name', 'last_name' ,'national_id']); - } - - $user->update($data); + $user->update($request->validated()); return $this->successResponse(); } diff --git a/backend/app/User/Requests/Profile/EditRequest.php b/backend/app/User/Requests/Profile/EditRequest.php index 4b0e1eb4..3f7468bd 100755 --- a/backend/app/User/Requests/Profile/EditRequest.php +++ b/backend/app/User/Requests/Profile/EditRequest.php @@ -25,31 +25,17 @@ class EditRequest extends FormRequest public function rules(): array { $user = $this->user(); - $isKycApproved = (int) $user->kyc_status === 2; - + return [ 'first_name' => [ - Rule::prohibitedIf($isKycApproved), - 'required', - 'string', - 'max:255', + Rule::prohibitedIf($user->kyc_status == 1), 'string', ], - 'last_name' => [ - Rule::prohibitedIf($isKycApproved), - 'required', - 'string', - 'max:255', + Rule::prohibitedIf($user->kyc_status == 1), 'string', ], - 'national_id' => [ - Rule::prohibitedIf($isKycApproved), - 'required', - 'string', - 'max:20', - Rule::unique('users', 'national_id')->ignore($user->id), + Rule::prohibitedIf($user->kyc_status == 1), 'string', ], - 'address' => 'required', 'postal_code' => 'required', 'phone_number' => 'required', @@ -62,9 +48,21 @@ class EditRequest extends FormRequest return [ function (Validator $validator) { $user = $this->user(); + if ($user->kyc_status == 1) { + $lockedFields = [ + 'first_name', + 'last_name', + 'national_id', + ]; - if ( $user->kyc_status === 1) { - if + foreach ($lockedFields as $field) { + if ($this->filled($field)) { + $validator->errors()->add( + $field, + 'you cannot change this field.' + ); + } + } } }, ]; -- 2.49.1 From 5ea986db40b22e85cfcafcb2c240614d69c42a54 Mon Sep 17 00:00:00 2001 From: amirghasempoor Date: Mon, 18 May 2026 14:34:28 +0330 Subject: [PATCH 45/61] separate any gateways --- .../{GatewayCategory.php => BusinessType.php} | 6 +-- backend/app/Models/FibTransaction.php | 12 +++++ backend/app/Models/FibTransactionStatus.php | 12 +++++ .../Models/{Gateway.php => Transaction.php} | 6 +-- backend/app/Models/UserGateway.php | 15 +++++++ ...oryFactory.php => BusinessTypeFactory.php} | 6 +-- .../factories/FibTransactionFactory.php | 24 ++++++++++ .../factories/FibTransactionStatusFactory.php | 24 ++++++++++ ...ewayFactory.php => TransactionFactory.php} | 6 +-- .../database/factories/UserGatewayFactory.php | 24 ++++++++++ ...17_053831_create_business_types_table.php} | 4 +- ..._17_053832_create_user_gateways_table.php} | 8 ++-- ...026_05_17_053833_create_payments_table.php | 2 +- ...05_18_095610_create_transactions_table.php | 37 +++++++++++++++ ..._create_fib_transaction_statuses_table.php | 28 ++++++++++++ ...8_104524_create_fib_transactions_table.php | 45 +++++++++++++++++++ ...egorySeeder.php => BusinessTypeSeeder.php} | 2 +- .../database/seeders/FibTransactionSeeder.php | 17 +++++++ .../seeders/FibTransactionStatusSeeder.php | 17 +++++++ ...atewaySeeder.php => TransactionSeeder.php} | 2 +- .../database/seeders/UserGatewaySeeder.php | 17 +++++++ 21 files changed, 293 insertions(+), 21 deletions(-) rename backend/app/Models/{GatewayCategory.php => BusinessType.php} (57%) create mode 100644 backend/app/Models/FibTransaction.php create mode 100644 backend/app/Models/FibTransactionStatus.php rename backend/app/Models/{Gateway.php => Transaction.php} (60%) create mode 100644 backend/app/Models/UserGateway.php rename backend/database/factories/{GatewayCategoryFactory.php => BusinessTypeFactory.php} (72%) create mode 100644 backend/database/factories/FibTransactionFactory.php create mode 100644 backend/database/factories/FibTransactionStatusFactory.php rename backend/database/factories/{GatewayFactory.php => TransactionFactory.php} (74%) create mode 100644 backend/database/factories/UserGatewayFactory.php rename backend/database/migrations/{2026_05_17_053831_create_gateway_categories_table.php => 2026_05_17_053831_create_business_types_table.php} (78%) rename backend/database/migrations/{2026_05_17_053832_create_gateways_table.php => 2026_05_17_053832_create_user_gateways_table.php} (76%) create mode 100644 backend/database/migrations/2026_05_18_095610_create_transactions_table.php create mode 100644 backend/database/migrations/2026_05_18_104523_create_fib_transaction_statuses_table.php create mode 100644 backend/database/migrations/2026_05_18_104524_create_fib_transactions_table.php rename backend/database/seeders/{GatewayCategorySeeder.php => BusinessTypeSeeder.php} (84%) create mode 100644 backend/database/seeders/FibTransactionSeeder.php create mode 100644 backend/database/seeders/FibTransactionStatusSeeder.php rename backend/database/seeders/{GatewaySeeder.php => TransactionSeeder.php} (85%) create mode 100644 backend/database/seeders/UserGatewaySeeder.php diff --git a/backend/app/Models/GatewayCategory.php b/backend/app/Models/BusinessType.php similarity index 57% rename from backend/app/Models/GatewayCategory.php rename to backend/app/Models/BusinessType.php index dd1e29df..c5b42f2e 100644 --- a/backend/app/Models/GatewayCategory.php +++ b/backend/app/Models/BusinessType.php @@ -2,13 +2,13 @@ namespace App\Models; -use Database\Factories\GatewayCategoryFactory; +use Database\Factories\BusinessTypeFactory; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; -class GatewayCategory extends Model +class BusinessType extends Model { - /** @use HasFactory */ + /** @use HasFactory */ use HasFactory; protected $guarded = ['id']; diff --git a/backend/app/Models/FibTransaction.php b/backend/app/Models/FibTransaction.php new file mode 100644 index 00000000..23c37102 --- /dev/null +++ b/backend/app/Models/FibTransaction.php @@ -0,0 +1,12 @@ + */ + use HasFactory; +} diff --git a/backend/app/Models/FibTransactionStatus.php b/backend/app/Models/FibTransactionStatus.php new file mode 100644 index 00000000..1f7d4125 --- /dev/null +++ b/backend/app/Models/FibTransactionStatus.php @@ -0,0 +1,12 @@ + */ + use HasFactory; +} diff --git a/backend/app/Models/Gateway.php b/backend/app/Models/Transaction.php similarity index 60% rename from backend/app/Models/Gateway.php rename to backend/app/Models/Transaction.php index d7b58f9c..a685ed3d 100644 --- a/backend/app/Models/Gateway.php +++ b/backend/app/Models/Transaction.php @@ -2,13 +2,13 @@ namespace App\Models; -use Database\Factories\GatewayFactory; +use Database\Factories\TransactionFactory; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; -class Gateway extends Model +class Transaction extends Model { - /** @use HasFactory */ + /** @use HasFactory */ use HasFactory; protected $guarded = ['id']; diff --git a/backend/app/Models/UserGateway.php b/backend/app/Models/UserGateway.php new file mode 100644 index 00000000..c1bd6f5e --- /dev/null +++ b/backend/app/Models/UserGateway.php @@ -0,0 +1,15 @@ + */ + use HasFactory; + + protected $guarded = ['id']; +} diff --git a/backend/database/factories/GatewayCategoryFactory.php b/backend/database/factories/BusinessTypeFactory.php similarity index 72% rename from backend/database/factories/GatewayCategoryFactory.php rename to backend/database/factories/BusinessTypeFactory.php index facefb5e..e4f420cb 100644 --- a/backend/database/factories/GatewayCategoryFactory.php +++ b/backend/database/factories/BusinessTypeFactory.php @@ -2,13 +2,13 @@ namespace Database\Factories; -use App\Models\GatewayCategory; +use App\Models\BusinessType; use Illuminate\Database\Eloquent\Factories\Factory; /** - * @extends Factory + * @extends Factory */ -class GatewayCategoryFactory extends Factory +class BusinessTypeFactory extends Factory { /** * Define the model's default state. diff --git a/backend/database/factories/FibTransactionFactory.php b/backend/database/factories/FibTransactionFactory.php new file mode 100644 index 00000000..5fc97df6 --- /dev/null +++ b/backend/database/factories/FibTransactionFactory.php @@ -0,0 +1,24 @@ + + */ +class FibTransactionFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + // + ]; + } +} diff --git a/backend/database/factories/FibTransactionStatusFactory.php b/backend/database/factories/FibTransactionStatusFactory.php new file mode 100644 index 00000000..94676e04 --- /dev/null +++ b/backend/database/factories/FibTransactionStatusFactory.php @@ -0,0 +1,24 @@ + + */ +class FibTransactionStatusFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + // + ]; + } +} diff --git a/backend/database/factories/GatewayFactory.php b/backend/database/factories/TransactionFactory.php similarity index 74% rename from backend/database/factories/GatewayFactory.php rename to backend/database/factories/TransactionFactory.php index f8029ab5..9f46bf36 100644 --- a/backend/database/factories/GatewayFactory.php +++ b/backend/database/factories/TransactionFactory.php @@ -2,13 +2,13 @@ namespace Database\Factories; -use App\Models\Gateway; +use App\Models\Transaction; use Illuminate\Database\Eloquent\Factories\Factory; /** - * @extends Factory + * @extends Factory */ -class GatewayFactory extends Factory +class TransactionFactory extends Factory { /** * Define the model's default state. diff --git a/backend/database/factories/UserGatewayFactory.php b/backend/database/factories/UserGatewayFactory.php new file mode 100644 index 00000000..46062b0f --- /dev/null +++ b/backend/database/factories/UserGatewayFactory.php @@ -0,0 +1,24 @@ + + */ +class UserGatewayFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + // + ]; + } +} diff --git a/backend/database/migrations/2026_05_17_053831_create_gateway_categories_table.php b/backend/database/migrations/2026_05_17_053831_create_business_types_table.php similarity index 78% rename from backend/database/migrations/2026_05_17_053831_create_gateway_categories_table.php rename to backend/database/migrations/2026_05_17_053831_create_business_types_table.php index 16ee53ea..61e834f5 100644 --- a/backend/database/migrations/2026_05_17_053831_create_gateway_categories_table.php +++ b/backend/database/migrations/2026_05_17_053831_create_business_types_table.php @@ -11,7 +11,7 @@ return new class extends Migration */ public function up(): void { - Schema::create('gateway_categories', function (Blueprint $table) { + Schema::create('business_types', function (Blueprint $table) { $table->id(); $table->string('name'); $table->timestamps(); @@ -23,6 +23,6 @@ return new class extends Migration */ public function down(): void { - Schema::dropIfExists('gateway_categories'); + Schema::dropIfExists('business_types'); } }; diff --git a/backend/database/migrations/2026_05_17_053832_create_gateways_table.php b/backend/database/migrations/2026_05_17_053832_create_user_gateways_table.php similarity index 76% rename from backend/database/migrations/2026_05_17_053832_create_gateways_table.php rename to backend/database/migrations/2026_05_17_053832_create_user_gateways_table.php index faab5d50..d72d530b 100644 --- a/backend/database/migrations/2026_05_17_053832_create_gateways_table.php +++ b/backend/database/migrations/2026_05_17_053832_create_user_gateways_table.php @@ -11,7 +11,7 @@ return new class extends Migration */ public function up(): void { - Schema::create('gateways', function (Blueprint $table) { + Schema::create('user_gateways', function (Blueprint $table) { $table->id(); $table->foreignId('user_id')->constrained('users'); $table->string('username'); @@ -22,8 +22,8 @@ return new class extends Migration $table->string('account_holder'); $table->string('logo')->nullable(); $table->string('iban'); - $table->foreignId('category_id')->constrained('gateway_categories'); - $table->string('category_name'); + $table->foreignId('business_type_id')->constrained('business_types'); + $table->string('business_type_name'); $table->timestamps(); }); } @@ -33,6 +33,6 @@ return new class extends Migration */ public function down(): void { - Schema::dropIfExists('gateways'); + Schema::dropIfExists('user_gateways'); } }; diff --git a/backend/database/migrations/2026_05_17_053833_create_payments_table.php b/backend/database/migrations/2026_05_17_053833_create_payments_table.php index 0bf5cc2e..815bfa82 100644 --- a/backend/database/migrations/2026_05_17_053833_create_payments_table.php +++ b/backend/database/migrations/2026_05_17_053833_create_payments_table.php @@ -23,7 +23,7 @@ return new class extends Migration $table->foreignId('status_id')->constrained('payment_statuses'); $table->string('status_name'); $table->ipAddress('ip'); - $table->foreignId('gateway_id')->constrained('gateways'); + $table->foreignId('user_gateway_id')->constrained('user_gateways'); $table->timestamps(); }); } diff --git a/backend/database/migrations/2026_05_18_095610_create_transactions_table.php b/backend/database/migrations/2026_05_18_095610_create_transactions_table.php new file mode 100644 index 00000000..7176a51f --- /dev/null +++ b/backend/database/migrations/2026_05_18_095610_create_transactions_table.php @@ -0,0 +1,37 @@ +id(); + $table->foreignId('user_id')->constrained('users'); + $table->string('amount'); + $table->string('currency'); + $table->string('callback_url'); + $table->string('description'); + $table->dateTime('expires_in'); + $table->foreignId('payment_id')->constrained('payments'); + $table->string('payment_track_id'); + $table->foreignId('gateway_id')->constrained('gateways'); + $table->integer('status'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('transactions'); + } +}; diff --git a/backend/database/migrations/2026_05_18_104523_create_fib_transaction_statuses_table.php b/backend/database/migrations/2026_05_18_104523_create_fib_transaction_statuses_table.php new file mode 100644 index 00000000..f984ce18 --- /dev/null +++ b/backend/database/migrations/2026_05_18_104523_create_fib_transaction_statuses_table.php @@ -0,0 +1,28 @@ +id(); + $table->string('name'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('fib_transaction_statuses'); + } +}; diff --git a/backend/database/migrations/2026_05_18_104524_create_fib_transactions_table.php b/backend/database/migrations/2026_05_18_104524_create_fib_transactions_table.php new file mode 100644 index 00000000..3e914953 --- /dev/null +++ b/backend/database/migrations/2026_05_18_104524_create_fib_transactions_table.php @@ -0,0 +1,45 @@ +id(); + $table->foreignId('transaction_id')->constrained('transactions'); + $table->text('qrcode'); + $table->string('readable_code'); + $table->string('personalAppLink'); + $table->string('businessAppLink'); + $table->string('corporateAppLink'); + $table->string('paymentId'); + $table->string('paymentMethod'); + $table->timestamp('validUntil'); + $table->foreignId('status_id')->constrained('fib_transaction_statuses'); + $table->string('status_name'); + $table->timestamp('paidAt'); + $table->string('paidAmount'); + $table->string('paidCurrency'); + $table->string('decliningReason'); + $table->timestamp('declinedAt'); + $table->string('paidByName'); + $table->string('paidByIban'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('fib_transactions'); + } +}; diff --git a/backend/database/seeders/GatewayCategorySeeder.php b/backend/database/seeders/BusinessTypeSeeder.php similarity index 84% rename from backend/database/seeders/GatewayCategorySeeder.php rename to backend/database/seeders/BusinessTypeSeeder.php index 6e41f3b8..d73d8340 100644 --- a/backend/database/seeders/GatewayCategorySeeder.php +++ b/backend/database/seeders/BusinessTypeSeeder.php @@ -5,7 +5,7 @@ namespace Database\Seeders; use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; -class GatewayCategorySeeder extends Seeder +class BusinessTypeSeeder extends Seeder { /** * Run the database seeds. diff --git a/backend/database/seeders/FibTransactionSeeder.php b/backend/database/seeders/FibTransactionSeeder.php new file mode 100644 index 00000000..f7749949 --- /dev/null +++ b/backend/database/seeders/FibTransactionSeeder.php @@ -0,0 +1,17 @@ + Date: Mon, 18 May 2026 16:28:58 +0330 Subject: [PATCH 46/61] create Enum and seeder for BusinessType and payment_statuses --- backend/app/Enums/BusinessTypeEnum.php | 43 +++++++++++++++++++ backend/app/Enums/PaymentStatusEnum.php | 23 ++++++++++ .../database/seeders/BusinessTypeSeeder.php | 19 +++++++- .../database/seeders/PaymentStatusSeeder.php | 9 +++- 4 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 backend/app/Enums/BusinessTypeEnum.php create mode 100644 backend/app/Enums/PaymentStatusEnum.php diff --git a/backend/app/Enums/BusinessTypeEnum.php b/backend/app/Enums/BusinessTypeEnum.php new file mode 100644 index 00000000..dcd453de --- /dev/null +++ b/backend/app/Enums/BusinessTypeEnum.php @@ -0,0 +1,43 @@ + 'medical', + self::LEGAL => 'legal', + self::ENGINEERING => 'engineering', + self::EDUCATION => 'education', + self::CONSULTING => 'consulting', + self::BEAUTY => 'beauty', + self::TECHNICAL_SERVICES => 'technical_services', + self::FINANCIAL => 'financial', + self::REAL_ESTATE => 'real_estate', + self::RESTAURANT => 'restaurant', + self::SHOP => 'shop', + self::TRANSPORTATION => 'transportation', + self::ART => 'art', + self::SPORT => 'sport', + self::OTHER => 'other', + }; + } +} diff --git a/backend/app/Enums/PaymentStatusEnum.php b/backend/app/Enums/PaymentStatusEnum.php new file mode 100644 index 00000000..78be8b94 --- /dev/null +++ b/backend/app/Enums/PaymentStatusEnum.php @@ -0,0 +1,23 @@ + 'unpaid', + self::Cancelled => 'cancelled', + self::Expired => 'expired', + self::Paid => 'paid', + self::Refunded => 'refunded', + }; + } +} diff --git a/backend/database/seeders/BusinessTypeSeeder.php b/backend/database/seeders/BusinessTypeSeeder.php index d73d8340..01726e9f 100644 --- a/backend/database/seeders/BusinessTypeSeeder.php +++ b/backend/database/seeders/BusinessTypeSeeder.php @@ -4,6 +4,7 @@ namespace Database\Seeders; use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; +use Illuminate\Support\Facades\DB; class BusinessTypeSeeder extends Seeder { @@ -12,6 +13,22 @@ class BusinessTypeSeeder extends Seeder */ public function run(): void { - // + DB::table('business_types')->insert([ + ['id' => 1, 'name' => 'medical', 'created_at' => now(), 'updated_at' => now()], + ['id' => 2, 'name' => 'legal', 'created_at' => now(), 'updated_at' => now()], + ['id' => 3, 'name' => 'engineering', 'created_at' => now(), 'updated_at' => now()], + ['id' => 4, 'name' => 'education', 'created_at' => now(), 'updated_at' => now()], + ['id' => 5, 'name' => 'consulting', 'created_at' => now(), 'updated_at' => now()], + ['id' => 6, 'name' => 'beauty', 'created_at' => now(), 'updated_at' => now()], + ['id' => 7, 'name' => 'technical_services', 'created_at' => now(), 'updated_at' => now()], + ['id' => 8, 'name' => 'financial', 'created_at' => now(), 'updated_at' => now()], + ['id' => 9, 'name' => 'real_estate', 'created_at' => now(), 'updated_at' => now()], + ['id' => 10, 'name' => 'restaurant', 'created_at' => now(), 'updated_at' => now()], + ['id' => 11, 'name' => 'shop', 'created_at' => now(), 'updated_at' => now()], + ['id' => 12, 'name' => 'transportation', 'created_at' => now(), 'updated_at' => now()], + ['id' => 13, 'name' => 'art', 'created_at' => now(), 'updated_at' => now()], + ['id' => 14, 'name' => 'sport', 'created_at' => now(), 'updated_at' => now()], + ['id' => 15, 'name' => 'other', 'created_at' => now(), 'updated_at' => now()], + ]); } } diff --git a/backend/database/seeders/PaymentStatusSeeder.php b/backend/database/seeders/PaymentStatusSeeder.php index 5550baae..875ff488 100644 --- a/backend/database/seeders/PaymentStatusSeeder.php +++ b/backend/database/seeders/PaymentStatusSeeder.php @@ -4,6 +4,7 @@ namespace Database\Seeders; use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; +use Illuminate\Support\Facades\DB; class PaymentStatusSeeder extends Seeder { @@ -12,6 +13,12 @@ class PaymentStatusSeeder extends Seeder */ public function run(): void { - // + DB::table('payment_statuses')->insert([ + ['id' => 0, 'name' => 'unpaid', 'created_at' => now(), 'updated_at' => now(),], + ['id' => 1, 'name' => 'cancelled', 'created_at' => now(), 'updated_at' => now(),], + ['id' => 2, 'name' => 'expired', 'created_at' => now(), 'updated_at' => now(),], + ['id' => 3, 'name' => 'paid', 'created_at' => now(), 'updated_at' => now(),], + ['id' => 4, 'name' => 'refunded', 'created_at' => now(), 'updated_at' => now(),], + ]); } } -- 2.49.1 From a7fa1097b7c6de60254ca64aef7cec5ca465e50a Mon Sep 17 00:00:00 2001 From: amirghasempoor Date: Wed, 20 May 2026 09:14:06 +0330 Subject: [PATCH 47/61] create user gateway controller --- .../ExpertManagementController.php | 1 - backend/app/Enums/BusinessTypeEnum.php | 23 +++++ .../GatewayManagementController.php | 87 +++++++++++++++++++ .../User/Requests/Gateway/StoreRequest.php | 36 ++++++++ .../User/Requests/Gateway/UpdateRequest.php | 32 +++++++ backend/routes/expert.php | 3 + backend/routes/web.php | 13 +++ 7 files changed, 194 insertions(+), 1 deletion(-) create mode 100644 backend/app/User/Controllers/GatewayManagementController.php create mode 100644 backend/app/User/Requests/Gateway/StoreRequest.php create mode 100644 backend/app/User/Requests/Gateway/UpdateRequest.php diff --git a/backend/app/Admin/Controllers/ExpertManagementController.php b/backend/app/Admin/Controllers/ExpertManagementController.php index a3e0c989..f0edad53 100755 --- a/backend/app/Admin/Controllers/ExpertManagementController.php +++ b/backend/app/Admin/Controllers/ExpertManagementController.php @@ -4,7 +4,6 @@ namespace App\Admin\Controllers; use App\Admin\Requests\ExpertManagement\StoreRequest; use App\Admin\Requests\ExpertManagement\UpdateRequest; -use App\Admin\Resources\ExpertManagementResource; use App\Facades\DataTable\DataTableFacade; use App\Http\Controllers\Controller; use App\Models\City; diff --git a/backend/app/Enums/BusinessTypeEnum.php b/backend/app/Enums/BusinessTypeEnum.php index dcd453de..232cc49f 100644 --- a/backend/app/Enums/BusinessTypeEnum.php +++ b/backend/app/Enums/BusinessTypeEnum.php @@ -40,4 +40,27 @@ enum BusinessTypeEnum: int self::OTHER => 'other', }; } + + public function name($id): string + { + $mapArray = [ + 1 => 'medical', + 2 => 'legal', + 3 => 'engineering', + 4 => 'education', + 5 => 'consulting', + 6 => 'beauty', + 7 => 'technical_services', + 8 => 'financial', + 9 => 'real_estate', + 10 => 'restaurant', + 11 => 'shop', + 12 => 'transportation', + 13 => 'art', + 14 => 'sport', + 15 => 'other', + ]; + + return $mapArray[$id]; + } } diff --git a/backend/app/User/Controllers/GatewayManagementController.php b/backend/app/User/Controllers/GatewayManagementController.php new file mode 100644 index 00000000..41d62b8c --- /dev/null +++ b/backend/app/User/Controllers/GatewayManagementController.php @@ -0,0 +1,87 @@ +where('user_id', '=', Auth::guard('web')->id()), + $request, + allowedFilters: ['*'], + allowedSortings: ['*'], + allowedSelects: ['id', 'name', 'domain', 'tax_id', 'bank_name', 'account_holder', 'iban', 'business_type_id', 'business_type_name'], + ); + } + + public function store(StoreRequest $request): JsonResponse + { + $user = Auth::guard('web')->user(); + + $logo = $request->has('logo') ? FileFacade::save($request->file('logo'), "{$user->id}") : null; + + UserGateway::query()->create([ + 'user_id' => $user->id, + 'username' => $user->username, + 'name' => $request->name, + 'domain' => $request->domain, + 'tax_id' => $request->tax_id, + 'bank_name' => $request->bank_name, + 'account_holder' => $request->account_holder, + 'iban' => $request->iban, + 'business_type_id' => $request->business_type_id, + 'business_type_name' => BusinessTypeEnum::name($request->business_type_id), + 'logo' => $logo, + ]); + + return $this->successResponse(); + } + + public function show(UserGateway $gateway): JsonResponse + { + return $this->successResponse($gateway); + } + + public function update(UpdateRequest $request, UserGateway $gateway): JsonResponse + { + $logo = null; + $user = Auth::guard('web')->user(); + + if ($request->has('logo')) + { + FileFacade::delete($gateway->logo, true); + $logo = FileFacade::save(request()->file('logo'), "{$user->id}"); + } + + $gateway->update([ + 'name' => $request->name, + 'domain' => $request->domain, + 'business_type_id' => $request->business_type_id, + 'business_type_name' => BusinessTypeEnum::name($request->business_type_id), + 'logo' => $logo, + ]); + + return $this->successResponse(); + } + + public function destroy(UserGateway $gateway): JsonResponse + { + $gateway->delete(); + return $this->successResponse(); + } +} diff --git a/backend/app/User/Requests/Gateway/StoreRequest.php b/backend/app/User/Requests/Gateway/StoreRequest.php new file mode 100644 index 00000000..2dcc0faa --- /dev/null +++ b/backend/app/User/Requests/Gateway/StoreRequest.php @@ -0,0 +1,36 @@ + + */ + public function rules(): array + { + return [ + 'name' => 'required|string|max:255', + 'domain' => 'required|string|max:255', + 'tax_id' => 'required|string', + 'bank_name' => 'required|string|max:255', + 'account_holder' => 'required|string', + 'iban' => 'required|string|max:255', + 'logo' => 'image|mimes:jpeg,png,jpg,gif,svg|max:2048', + 'business_type_id' => 'required|string|exists:business_types,id', + ]; + } +} diff --git a/backend/app/User/Requests/Gateway/UpdateRequest.php b/backend/app/User/Requests/Gateway/UpdateRequest.php new file mode 100644 index 00000000..30db4db1 --- /dev/null +++ b/backend/app/User/Requests/Gateway/UpdateRequest.php @@ -0,0 +1,32 @@ + + */ + public function rules(): array + { + return [ + 'name' => 'required|string|max:255', + 'domain' => 'required|string|max:255', + 'business_type_id' => 'required|string|exists:business_types,id', + 'logo' => 'image|mimes:jpeg,png,jpg,gif,svg|max:2048', + ]; + } +} diff --git a/backend/routes/expert.php b/backend/routes/expert.php index 50400564..3597a42f 100755 --- a/backend/routes/expert.php +++ b/backend/routes/expert.php @@ -38,6 +38,7 @@ Route::prefix('user_management') Route::post('/{user}', 'update')->name('update'); Route::delete('/{user}', 'destroy')->name('destroy'); }); + Route::prefix('roles') ->name('roles.') ->middleware(['auth:expert', 'permission:role-management']) @@ -49,6 +50,7 @@ Route::prefix('roles') Route::post('/{role}', 'update')->name('update'); Route::delete('/{role}', 'destroy')->name('destroy'); }); + Route::prefix('permissions') ->name('permissions.') ->middleware(['auth:expert', 'permission:permission-management']) @@ -60,6 +62,7 @@ Route::prefix('permissions') Route::post('/{permission}', 'update')->name('update'); Route::delete('/{permission}', 'destroy')->name('destroy'); }); + Route::prefix('expert') ->name('expert.') ->middleware(['auth:expert', 'permission:expert-management']) diff --git a/backend/routes/web.php b/backend/routes/web.php index 28784019..b4048d81 100755 --- a/backend/routes/web.php +++ b/backend/routes/web.php @@ -5,6 +5,7 @@ use App\Admin\Controllers\PermissionManagementController; use App\Admin\Controllers\RoleManagementController; use App\Admin\Controllers\UserManagementController; use App\User\Controllers\AuthController; +use App\User\Controllers\GatewayManagementController; use App\User\Controllers\ProfileController; use Illuminate\Support\Facades\Route; @@ -26,3 +27,15 @@ Route::prefix('profile') Route::post('edit', 'edit')->name('edit'); Route::post('change_password', 'changePassword')->name('changePassword'); }); + +Route::prefix('user_gateway') + ->name('userGateway.') + ->middleware(['auth']) + ->controller(GatewayManagementController::class) + ->group(function () { + Route::get('/', 'index')->name('index'); + Route::post('/', 'store')->name('store'); + Route::get('/{gateway}', 'show')->name('show'); + Route::post('/{gateway}', 'update')->name('update'); + Route::delete('/{gateway}', 'destroy')->name('destroy'); + }); -- 2.49.1 From 76e6ab4e0cd14f89cb717aa1f12b3446cbd4d7b2 Mon Sep 17 00:00:00 2001 From: amirghasempoor Date: Wed, 20 May 2026 09:53:05 +0330 Subject: [PATCH 48/61] change edit method --- .../Admin/Requests/Profile/EditRequest.php | 1 - .../GatewayManagementController.php | 36 +++++++++----- .../User/Controllers/ProfileController.php | 20 +++++++- .../User/Requests/Gateway/StoreRequest.php | 3 +- .../User/Requests/Gateway/UpdateRequest.php | 3 +- .../app/User/Requests/Profile/EditRequest.php | 47 +++++-------------- .../0001_01_01_000000_create_users_table.php | 1 - 7 files changed, 57 insertions(+), 54 deletions(-) diff --git a/backend/app/Admin/Requests/Profile/EditRequest.php b/backend/app/Admin/Requests/Profile/EditRequest.php index ccc056b4..6b997c9e 100755 --- a/backend/app/Admin/Requests/Profile/EditRequest.php +++ b/backend/app/Admin/Requests/Profile/EditRequest.php @@ -24,7 +24,6 @@ class EditRequest extends FormRequest */ public function rules(): array { - $user = $this->user(); return [ 'username' => 'string|max:255', 'first_name' => 'required|string', diff --git a/backend/app/User/Controllers/GatewayManagementController.php b/backend/app/User/Controllers/GatewayManagementController.php index 41d62b8c..690d364e 100644 --- a/backend/app/User/Controllers/GatewayManagementController.php +++ b/backend/app/User/Controllers/GatewayManagementController.php @@ -13,6 +13,8 @@ use App\User\Requests\Gateway\UpdateRequest; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\DB; +use Throwable; class GatewayManagementController extends Controller { @@ -29,25 +31,33 @@ class GatewayManagementController extends Controller ); } + /** + * @throws Throwable + */ public function store(StoreRequest $request): JsonResponse { $user = Auth::guard('web')->user(); $logo = $request->has('logo') ? FileFacade::save($request->file('logo'), "{$user->id}") : null; - UserGateway::query()->create([ - 'user_id' => $user->id, - 'username' => $user->username, - 'name' => $request->name, - 'domain' => $request->domain, - 'tax_id' => $request->tax_id, - 'bank_name' => $request->bank_name, - 'account_holder' => $request->account_holder, - 'iban' => $request->iban, - 'business_type_id' => $request->business_type_id, - 'business_type_name' => BusinessTypeEnum::name($request->business_type_id), - 'logo' => $logo, - ]); + DB::transaction(function () use ($user, $logo, $request) { + UserGateway::query()->create([ + 'user_id' => $user->id, + 'username' => $user->username, + 'name' => $request->name, + 'domain' => $request->domain, + 'tax_id' => $request->tax_id, + 'bank_name' => $request->bank_name, + 'account_holder' => $request->account_holder, + 'iban' => $request->iban, + 'business_type_id' => $request->business_type_id, + 'business_type_name' => BusinessTypeEnum::name($request->business_type_id), + 'logo' => $logo, + ]); + + $user->update(['authority_level' => 1]); + }); + return $this->successResponse(); } diff --git a/backend/app/User/Controllers/ProfileController.php b/backend/app/User/Controllers/ProfileController.php index 3d89e5dc..ef007a21 100755 --- a/backend/app/User/Controllers/ProfileController.php +++ b/backend/app/User/Controllers/ProfileController.php @@ -3,6 +3,8 @@ namespace App\User\Controllers; use App\Http\Controllers\Controller; +use App\Models\City; +use App\Models\Province; use App\Traits\ApiResponse; use App\User\Requests\Profile\ChangePasswordRequest; use App\User\Requests\Profile\EditRequest; @@ -23,8 +25,24 @@ class ProfileController extends Controller public function edit(EditRequest $request): JsonResponse { $user = Auth::user(); + $province = Province::query()->find($request->province_id); + $city = City::query()->find($request->city_id); - $user->update($request->validated()); + $user->update([ + 'first_name' => $request->first_name, + 'last_name' => $request->last_name, + 'national_id' => $request->national_id, + 'address' => $request->address, + 'postal_code' => $request->postal_code, + 'phone_number' => $request->phone_number, + 'province_id' => $province->id, + 'province_name' => $province->name, + 'city_id' => $city->id, + 'city_name' => $city->name, + 'birth_date' => $request->birth_date, + 'email' => $request->email, + 'kyc_status' => 1, + ]); return $this->successResponse(); } diff --git a/backend/app/User/Requests/Gateway/StoreRequest.php b/backend/app/User/Requests/Gateway/StoreRequest.php index 2dcc0faa..f7562dd8 100644 --- a/backend/app/User/Requests/Gateway/StoreRequest.php +++ b/backend/app/User/Requests/Gateway/StoreRequest.php @@ -4,6 +4,7 @@ namespace App\User\Requests\Gateway; use Illuminate\Contracts\Validation\ValidationRule; use Illuminate\Foundation\Http\FormRequest; +use Illuminate\Support\Facades\Auth; class StoreRequest extends FormRequest { @@ -12,7 +13,7 @@ class StoreRequest extends FormRequest */ public function authorize(): bool { - return true; + return Auth::guard('web')->user()->kyc_status; } /** diff --git a/backend/app/User/Requests/Gateway/UpdateRequest.php b/backend/app/User/Requests/Gateway/UpdateRequest.php index 30db4db1..749eb4da 100644 --- a/backend/app/User/Requests/Gateway/UpdateRequest.php +++ b/backend/app/User/Requests/Gateway/UpdateRequest.php @@ -4,6 +4,7 @@ namespace App\User\Requests\Gateway; use Illuminate\Contracts\Validation\ValidationRule; use Illuminate\Foundation\Http\FormRequest; +use Illuminate\Support\Facades\Auth; class UpdateRequest extends FormRequest { @@ -12,7 +13,7 @@ class UpdateRequest extends FormRequest */ public function authorize(): bool { - return true; + return Auth::guard('web')->user()->kyc_status; } /** diff --git a/backend/app/User/Requests/Profile/EditRequest.php b/backend/app/User/Requests/Profile/EditRequest.php index 3f7468bd..450ce3fa 100755 --- a/backend/app/User/Requests/Profile/EditRequest.php +++ b/backend/app/User/Requests/Profile/EditRequest.php @@ -4,6 +4,7 @@ namespace App\User\Requests\Profile; use Illuminate\Contracts\Validation\ValidationRule; use Illuminate\Foundation\Http\FormRequest; +use Illuminate\Support\Facades\Auth; use Illuminate\Validation\Rule; use Illuminate\Validation\Validator; @@ -24,47 +25,21 @@ class EditRequest extends FormRequest */ public function rules(): array { - $user = $this->user(); + $user = Auth::guard('web')->user(); return [ - 'first_name' => [ - Rule::prohibitedIf($user->kyc_status == 1), 'string', - ], - 'last_name' => [ - Rule::prohibitedIf($user->kyc_status == 1), 'string', - ], + 'first_name' => 'required|string|max:255', + 'last_name' => 'required|string|max:255', 'national_id' => [ Rule::prohibitedIf($user->kyc_status == 1), 'string', ], - 'address' => 'required', - 'postal_code' => 'required', - 'phone_number' => 'required', - 'province_id' => 'required', - 'city_id' => 'required', - ]; - } - public function after(): array - { - return [ - function (Validator $validator) { - $user = $this->user(); - if ($user->kyc_status == 1) { - $lockedFields = [ - 'first_name', - 'last_name', - 'national_id', - ]; - - foreach ($lockedFields as $field) { - if ($this->filled($field)) { - $validator->errors()->add( - $field, - 'you cannot change this field.' - ); - } - } - } - }, + 'email' => 'required|email|max:255', + 'address' => 'required|string', + 'postal_code' => 'required|string', + 'phone_number' => 'required|string', + 'province_id' => 'required|exists:provinces,id', + 'city_id' => 'required|exists:cities,id', + 'birth_date' => 'required|date', ]; } } diff --git a/backend/database/migrations/0001_01_01_000000_create_users_table.php b/backend/database/migrations/0001_01_01_000000_create_users_table.php index 0ac11763..124ca19a 100755 --- a/backend/database/migrations/0001_01_01_000000_create_users_table.php +++ b/backend/database/migrations/0001_01_01_000000_create_users_table.php @@ -22,7 +22,6 @@ return new class extends Migration $table->string('national_id')->nullable(); $table->string('address')->nullable(); $table->string('postal_code')->nullable(); - $table->string('tax_id')->nullable(); $table->string('phone_number')->nullable(); $table->date('birth_date')->nullable(); $table->foreignId('province_id')->nullable()->constrained('provinces'); -- 2.49.1 From 463ca3f006b794527e02e0a934bf9186ebe22f0c Mon Sep 17 00:00:00 2001 From: amirghasempoor Date: Wed, 20 May 2026 15:22:13 +0330 Subject: [PATCH 49/61] add id card to profile --- .../app/Services/FIB/AuthorizationService.php | 3 +- .../User/Controllers/ProfileController.php | 46 +++++++++++++------ .../app/User/Requests/Profile/EditRequest.php | 5 +- backend/app/User/Resources/UserResource.php | 12 +++++ backend/config/logging.php | 6 +++ ...05_18_095610_create_transactions_table.php | 2 +- 6 files changed, 53 insertions(+), 21 deletions(-) diff --git a/backend/app/Services/FIB/AuthorizationService.php b/backend/app/Services/FIB/AuthorizationService.php index 9867133f..8f1e40df 100755 --- a/backend/app/Services/FIB/AuthorizationService.php +++ b/backend/app/Services/FIB/AuthorizationService.php @@ -16,7 +16,7 @@ class AuthorizationService public function __construct() { $this->url = config('fib.authorization.url'); - $this->channelName = ''; + $this->channelName = 'fib_authorization'; $this->inputParameters = [ 'client_id' => config('fib.authorization.client_id'), 'client_secret' => config('fib.authorization.client_secret'), @@ -51,7 +51,6 @@ class AuthorizationService { return Http::asForm() ->throw() - ->withoutVerifying() ->post($this->url, $this->inputParameters) ->json(); } diff --git a/backend/app/User/Controllers/ProfileController.php b/backend/app/User/Controllers/ProfileController.php index ef007a21..f8fb83e2 100755 --- a/backend/app/User/Controllers/ProfileController.php +++ b/backend/app/User/Controllers/ProfileController.php @@ -2,6 +2,7 @@ namespace App\User\Controllers; +use App\Facades\File\FileFacade; use App\Http\Controllers\Controller; use App\Models\City; use App\Models\Province; @@ -11,7 +12,9 @@ use App\User\Requests\Profile\EditRequest; use App\User\Resources\UserResource; use Illuminate\Http\JsonResponse; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Hash; +use Throwable; class ProfileController extends Controller { @@ -22,27 +25,40 @@ class ProfileController extends Controller return $this->successResponse(new UserResource(Auth::user())); } + /** + * @throws Throwable + */ public function edit(EditRequest $request): JsonResponse { $user = Auth::user(); $province = Province::query()->find($request->province_id); $city = City::query()->find($request->city_id); - $user->update([ - 'first_name' => $request->first_name, - 'last_name' => $request->last_name, - 'national_id' => $request->national_id, - 'address' => $request->address, - 'postal_code' => $request->postal_code, - 'phone_number' => $request->phone_number, - 'province_id' => $province->id, - 'province_name' => $province->name, - 'city_id' => $city->id, - 'city_name' => $city->name, - 'birth_date' => $request->birth_date, - 'email' => $request->email, - 'kyc_status' => 1, - ]); + DB::transaction(function () use ($user, $province, $city, $request) { + if ($user->kyc_status == 0) + { + $user->documents()->create([ + 'title' => 'id_card', + 'url' => FileFacade::save($request->file('id_card'), "{$user->id}/"), + ]); + } + + $user->update([ + 'first_name' => $request->first_name, + 'last_name' => $request->last_name, + 'national_id' => $request->national_id, + 'address' => $request->address, + 'postal_code' => $request->postal_code, + 'phone_number' => $request->phone_number, + 'province_id' => $province->id, + 'province_name' => $province->name, + 'city_id' => $city->id, + 'city_name' => $city->name, + 'birth_date' => $request->birth_date, + 'email' => $request->email, + 'kyc_status' => 1, + ]); + }); return $this->successResponse(); } diff --git a/backend/app/User/Requests/Profile/EditRequest.php b/backend/app/User/Requests/Profile/EditRequest.php index 450ce3fa..0467ddc2 100755 --- a/backend/app/User/Requests/Profile/EditRequest.php +++ b/backend/app/User/Requests/Profile/EditRequest.php @@ -30,9 +30,7 @@ class EditRequest extends FormRequest return [ 'first_name' => 'required|string|max:255', 'last_name' => 'required|string|max:255', - 'national_id' => [ - Rule::prohibitedIf($user->kyc_status == 1), 'string', - ], + 'national_id' => [Rule::prohibitedIf($user->kyc_status == 1), 'string',], 'email' => 'required|email|max:255', 'address' => 'required|string', 'postal_code' => 'required|string', @@ -40,6 +38,7 @@ class EditRequest extends FormRequest 'province_id' => 'required|exists:provinces,id', 'city_id' => 'required|exists:cities,id', 'birth_date' => 'required|date', + 'id_card' => [Rule::prohibitedIf($user->kyc_status == 1), 'image', 'mimes:jpeg,png,jpg,gif,svg', 'max:2048'] ]; } } diff --git a/backend/app/User/Resources/UserResource.php b/backend/app/User/Resources/UserResource.php index 55f98563..d5df7636 100755 --- a/backend/app/User/Resources/UserResource.php +++ b/backend/app/User/Resources/UserResource.php @@ -18,6 +18,18 @@ class UserResource extends JsonResource 'id' => $this->id, 'username' => $this->username, 'email' => $this->email, + 'first_name' => $this->first_name, + 'last_name' => $this->last_name, + 'national_id' => $this->national_id, + 'address' => $this->address, + 'postal_code' => $this->postal_code, + 'phone_number' => $this->phone_number, + 'province_id' => $this->provonce_id, + 'province_name' => $this->province_name, + 'city_id' => $this->city_id, + 'city_name' => $this->city_name, + 'birth_date' => $this->birth_date, + 'kyc_status' => $this->kyc_status, ]; } } diff --git a/backend/config/logging.php b/backend/config/logging.php index b09cb25d..ee09ba9e 100755 --- a/backend/config/logging.php +++ b/backend/config/logging.php @@ -127,6 +127,12 @@ return [ 'path' => storage_path('logs/laravel.log'), ], + 'fib_authorization' => [ + 'driver' => 'single', + 'path' => storage_path('logs/fib/authorization.log'), + 'level' => 'error', + ], + ], ]; diff --git a/backend/database/migrations/2026_05_18_095610_create_transactions_table.php b/backend/database/migrations/2026_05_18_095610_create_transactions_table.php index 7176a51f..208f6f8e 100644 --- a/backend/database/migrations/2026_05_18_095610_create_transactions_table.php +++ b/backend/database/migrations/2026_05_18_095610_create_transactions_table.php @@ -21,7 +21,7 @@ return new class extends Migration $table->dateTime('expires_in'); $table->foreignId('payment_id')->constrained('payments'); $table->string('payment_track_id'); - $table->foreignId('gateway_id')->constrained('gateways'); + $table->foreignId('user_gateway_id')->constrained('user_gateways'); $table->integer('status'); $table->timestamps(); }); -- 2.49.1 From 77a4e980583e600ca265670d08e5049d7e6b9655 Mon Sep 17 00:00:00 2001 From: hasanzaki Date: Fri, 22 May 2026 16:28:10 +0330 Subject: [PATCH 50/61] stage things --- backend/app/Services/FIB/FIBClient.php | 54 +++++++++++++++++ backend/app/Services/Payments/FibAdapter.php | 37 ++++++++++++ .../app/Services/Payments/PaymentService.php | 37 ++++++++++++ .../Payment/DTO/PaymentCreateDTO.php | 58 +++++++++++++++++++ .../Controllers/Payment/PaymentController.php | 34 +++++++++++ backend/routes/api.php | 9 +++ docker-compose.yml | 6 +- 7 files changed, 232 insertions(+), 3 deletions(-) create mode 100755 backend/app/Services/FIB/FIBClient.php create mode 100644 backend/app/Services/Payments/FibAdapter.php create mode 100644 backend/app/Services/Payments/PaymentService.php create mode 100644 backend/app/User/Controllers/Payment/DTO/PaymentCreateDTO.php create mode 100644 backend/app/User/Controllers/Payment/PaymentController.php diff --git a/backend/app/Services/FIB/FIBClient.php b/backend/app/Services/FIB/FIBClient.php new file mode 100755 index 00000000..e96a7e9b --- /dev/null +++ b/backend/app/Services/FIB/FIBClient.php @@ -0,0 +1,54 @@ +contentType('application/json') + ->withToken($this->getAccessToken()) + ->timeout(30); + } + + public function getAccessToken(): string + { + try { + $response = Http::withOptions([ + 'proxy' => 'http://host.docker.internal:10808', + ])->asForm()->post('https://fib-stage.fib.iq/auth/realms/fib-online-shop/protocol/openid-connect/token', [ + 'grant_type' => 'client_credentials', + 'client_id' => 'ipay-test-payment', + 'client_secret' => '8dae3180-f39c-448e-8b45-a01c8f4c8c15', + ]); + + if ($response->failed()) { + throw new \Exception( + 'Unable to authenticate with FIB: ' . + $response->body() + ); + } + + $data = $response->json(); + + if (!isset($data['access_token'])) { + throw new \Exception('FIB token missing in response'); + } + + return $data['access_token']; + } catch (\Exception $exception) { + return $exception->getMessage(); + } + } +} diff --git a/backend/app/Services/Payments/FibAdapter.php b/backend/app/Services/Payments/FibAdapter.php new file mode 100644 index 00000000..0e6adb2b --- /dev/null +++ b/backend/app/Services/Payments/FibAdapter.php @@ -0,0 +1,37 @@ +create([ + 'user_id' => auth()->id(), + 'username' => $data['username'], + 'track_id' => $data['track_id'], + 'amount' => $data['amount'], + 'currency' => $data['currency'], + 'callback_url' => $data['callback_url'], + 'expires_at' => $data['expires_at'], + 'status_id' => $data['status_id'], + 'status_name' => $data['status_name'], + 'ip' => $data['ip'], + 'user_gateway_id' => $data['user_gateway_id'], + 'created_at' => $data['created_at'], + 'updated_at' => $data['updated_at'], + ]); + $this->fib_client->client(); + } +} diff --git a/backend/app/Services/Payments/PaymentService.php b/backend/app/Services/Payments/PaymentService.php new file mode 100644 index 00000000..e5dd86fa --- /dev/null +++ b/backend/app/Services/Payments/PaymentService.php @@ -0,0 +1,37 @@ +fib_client = new FibClient(); + } + + public function createPayment(PaymentCreateDTO $dto) + { +// Payment::query()->create([ +// 'user_id' => $dto->user_id, +// 'username' => $dto->username, +// 'track_id' => $dto->track_id, +// 'amount' => $dto->amount, +// 'currency' => $dto->currency, +// 'callback_url' => $dto->callback_url, +// 'expires_at' => $dto->expires_at, +// 'status_id' => $dto->status_id, +// 'status_name' => $dto->status_name, +// 'ip' => $dto->ip, +// 'user_gateway_id' => $dto->user_gateway_id, +// 'created_at' => $dto->created_at, +// 'updated_at' => $dto->updated_at, +// ]); + return $this->fib_client->getAccessToken(); + } +} diff --git a/backend/app/User/Controllers/Payment/DTO/PaymentCreateDTO.php b/backend/app/User/Controllers/Payment/DTO/PaymentCreateDTO.php new file mode 100644 index 00000000..30725e34 --- /dev/null +++ b/backend/app/User/Controllers/Payment/DTO/PaymentCreateDTO.php @@ -0,0 +1,58 @@ +username = $data['username'] ?? null; + $this->user_id = $data['user_id'] ?? null; + $this->track_id = $data['track_id'] ?? null; + $this->amount = $data['amount'] ?? null; + $this->currency = $data['currency'] ?? null; + $this->callback_url = $data['callback_url'] ?? null; + $this->expires_at = $data['expires_at'] ?? null; + $this->status_id = $data['status_id'] ?? null; + $this->status_name = $data['status_name'] ?? null; + $this->ip = $data['ip'] ?? null; + $this->user_gateway_id = $data['user_gateway_id'] ?? null; + $this->created_at = $data['created_at'] ?? null; + $this->updated_at = $data['updated_at'] ?? null; + } + + public static function fromRequest(Request $request): PaymentCreateDTO + { + return new self([ + 'username' => $request->username ?? null, + 'track_id' => $request->track_id ?? null, + 'amount' => $request->amount ?? null, + 'currency' => $request->currency ?? null, + 'callback_url' => $request->callback_url ?? null, + 'expires_at' => $request->expires_at ?? null, + 'status_id' => $request->status_id ?? null, + 'status_name' => $request->status_name ?? null, + 'ip' => $request->ip ?? null, + 'user_gateway_id' => $request->user_gateway_id ?? null, + 'created_at' => $request->created_at ?? null, + 'updated_at' => $request->updated_at ?? null, + ]); + } +} diff --git a/backend/app/User/Controllers/Payment/PaymentController.php b/backend/app/User/Controllers/Payment/PaymentController.php new file mode 100644 index 00000000..4ec9251b --- /dev/null +++ b/backend/app/User/Controllers/Payment/PaymentController.php @@ -0,0 +1,34 @@ +payment_service = $payment_service; + } + + public function createPayment(Request $request): JsonResponse + { + try { + $dto = PaymentCreateDTO::fromRequest($request); + $result = $this->payment_service->createPayment($dto); + return $this->successResponse($result); + } catch (\Exception $exception) { + throw $exception; + } + } +} diff --git a/backend/routes/api.php b/backend/routes/api.php index f35f6f84..a511350e 100755 --- a/backend/routes/api.php +++ b/backend/routes/api.php @@ -1,8 +1,17 @@ user(); })->middleware('auth:api'); + +Route::prefix('v1')->group(function () { + Route::prefix('payment')->group(function () { + Route::get('/create_payment', [PaymentController::class, 'createPayment']); + }); + +}); diff --git a/docker-compose.yml b/docker-compose.yml index d21e5ef4..a93a5001 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -40,9 +40,7 @@ services: - payment backend: - build: - context: ${BACKEND_PATH} - dockerfile: Dockerfile + image: payment-backend:latest container_name: laravel tty: true restart: unless-stopped @@ -54,6 +52,8 @@ services: - postgres networks: - payment + command: ["php", "artisan", "serve", "--host=0.0.0.0", "--port=9000"] + # Prints "Overridden command!" # logging: # driver: "syslog" # options: -- 2.49.1 From 738b21e18281798829cd53bbc3560ec39f9e9434 Mon Sep 17 00:00:00 2001 From: hasanzaki Date: Fri, 22 May 2026 19:18:11 +0330 Subject: [PATCH 51/61] just shape sth stage1 --- backend/app/Models/FibTransaction.php | 2 + backend/app/Services/FIB/FIBClient.php | 34 ++++++++- .../Payments/DTO/FIBPaymentCreateDTO.php | 38 ++++++++++ .../Payments/DTO/PaymentCreateDTO.php | 46 +++++++++++++ .../app/Services/Payments/PaymentService.php | 69 ++++++++++++++----- .../Payment/DTO/PaymentCreateDTO.php | 58 ---------------- .../Controllers/Payment/PaymentController.php | 5 +- .../Requests/Payment/FIB/StoreRequest.php | 36 ++++++++++ ...05_18_095610_create_transactions_table.php | 17 +++-- ...8_104524_create_fib_transactions_table.php | 35 +++++----- .../seeders/FibTransactionStatusSeeder.php | 9 ++- backend/routes/api.php | 8 ++- 12 files changed, 248 insertions(+), 109 deletions(-) create mode 100644 backend/app/Services/Payments/DTO/FIBPaymentCreateDTO.php create mode 100644 backend/app/Services/Payments/DTO/PaymentCreateDTO.php delete mode 100644 backend/app/User/Controllers/Payment/DTO/PaymentCreateDTO.php create mode 100644 backend/app/User/Requests/Payment/FIB/StoreRequest.php diff --git a/backend/app/Models/FibTransaction.php b/backend/app/Models/FibTransaction.php index 23c37102..c208c0a7 100644 --- a/backend/app/Models/FibTransaction.php +++ b/backend/app/Models/FibTransaction.php @@ -9,4 +9,6 @@ class FibTransaction extends Model { /** @use HasFactory<\Database\Factories\FibTransactionFactory> */ use HasFactory; + + protected $guarded = ['id']; } diff --git a/backend/app/Services/FIB/FIBClient.php b/backend/app/Services/FIB/FIBClient.php index e96a7e9b..be22b64c 100755 --- a/backend/app/Services/FIB/FIBClient.php +++ b/backend/app/Services/FIB/FIBClient.php @@ -22,9 +22,14 @@ class FIBClient ->timeout(30); } - public function getAccessToken(): string + public function getAccessToken() { try { + if ($this->isNotProduction()) { + return json_decode( + file_get_contents(storage_path('app/mock/fib/token.json')) + ); + } $response = Http::withOptions([ 'proxy' => 'http://host.docker.internal:10808', ])->asForm()->post('https://fib-stage.fib.iq/auth/realms/fib-online-shop/protocol/openid-connect/token', [ @@ -51,4 +56,31 @@ class FIBClient return $exception->getMessage(); } } + + public function createPaymentService() + { + if ($this->isNotProduction()) { + return json_decode( + file_get_contents(storage_path('app/mock/fib/payment_create_success.json')) + ); + } + $response = $this->client()->post("https://fib-stage.fib.iq/protected/v1/payments"); + if ($response->failed()) { + throw new \Exception( + 'Unable to authenticate with FIB: ' . + $response->body() + ); + } + + $data = $response->json(); + if (!isset($data['paymentId'])) { + throw new \Exception('FIB paymentId missing in response'); + } + return $data; + } + + public function isNotProduction() + { + return env('APP_ENV') === 'local'; + } } diff --git a/backend/app/Services/Payments/DTO/FIBPaymentCreateDTO.php b/backend/app/Services/Payments/DTO/FIBPaymentCreateDTO.php new file mode 100644 index 00000000..e09cab6b --- /dev/null +++ b/backend/app/Services/Payments/DTO/FIBPaymentCreateDTO.php @@ -0,0 +1,38 @@ +paymentId = $data['paymentId']; + $this->readableCode = $data['readableCode']; + $this->qrCode = $data['qrCode']; + $this->validUntil = $data['validUntil']; + $this->personalAppLink = $data['personalAppLink']; + $this->businessAppLink = $data['businessAppLink']; + $this->corporateAppLink = $data['corporateAppLink']; + } + + public static function fromResponse($data): FIBPaymentCreateDTO + { + return new self([ + 'paymentId' => $data->paymentId, + 'readableCode' => $data->readableCode, + 'qrCode' => $data->qrCode, + 'validUntil' => $data->validUntil, + 'personalAppLink' => $data->personalAppLink, + 'businessAppLink' => $data->businessAppLink, + 'corporateAppLink' => $data->corporateAppLink, + ]); + } +} diff --git a/backend/app/Services/Payments/DTO/PaymentCreateDTO.php b/backend/app/Services/Payments/DTO/PaymentCreateDTO.php new file mode 100644 index 00000000..a3129a3a --- /dev/null +++ b/backend/app/Services/Payments/DTO/PaymentCreateDTO.php @@ -0,0 +1,46 @@ +username = $data['username']; + $this->user_id = $data['user_id']; + $this->track_id = $data['track_id']; + $this->amount = $data['amount']; + $this->currency = $data['currency']; + $this->callback_url = $data['callback_url']; + $this->ip = $data['ip']; + $this->user_gateway_id = $data['user_gateway_id']; + + } + + public static function fromRequest(Request $request): PaymentCreateDTO + { + return new self([ + 'username' => $request->username, + 'user_id' => '2', + 'track_id' => uuid_create(), + 'amount' => $request->amount, + 'currency' => $request->currency, + 'callback_url' => $request->callback_url, + 'ip' => $request->getClientIp(), + 'user_gateway_id' => $request->user_gateway_id, + ]); + } +} diff --git a/backend/app/Services/Payments/PaymentService.php b/backend/app/Services/Payments/PaymentService.php index e5dd86fa..d50d8b7f 100644 --- a/backend/app/Services/Payments/PaymentService.php +++ b/backend/app/Services/Payments/PaymentService.php @@ -2,9 +2,13 @@ namespace App\Services\Payments; +use App\Enums\PaymentStatusEnum; +use App\Models\FibTransaction; use App\Models\Payment; +use App\Models\Transaction; use App\Services\FIB\FIBClient; -use App\User\Controllers\Payment\DTO\PaymentCreateDTO; +use App\Services\Payments\DTO\FIBPaymentCreateDTO; +use App\Services\Payments\DTO\PaymentCreateDTO; class PaymentService { @@ -17,21 +21,52 @@ class PaymentService public function createPayment(PaymentCreateDTO $dto) { -// Payment::query()->create([ -// 'user_id' => $dto->user_id, -// 'username' => $dto->username, -// 'track_id' => $dto->track_id, -// 'amount' => $dto->amount, -// 'currency' => $dto->currency, -// 'callback_url' => $dto->callback_url, -// 'expires_at' => $dto->expires_at, -// 'status_id' => $dto->status_id, -// 'status_name' => $dto->status_name, -// 'ip' => $dto->ip, -// 'user_gateway_id' => $dto->user_gateway_id, -// 'created_at' => $dto->created_at, -// 'updated_at' => $dto->updated_at, -// ]); - return $this->fib_client->getAccessToken(); + try { + $payment = Payment::query()->create([ + 'user_id' => $dto->user_id, + 'username' => $dto->username, + 'track_id' => $dto->track_id, + 'amount' => $dto->amount, + 'currency' => $dto->currency, + 'callback_url' => $dto->callback_url, + 'status_id' => PaymentStatusEnum::Unpaid->value, + 'status_name' => PaymentStatusEnum::Unpaid->name, + 'ip' => $dto->ip, + 'user_gateway_id' => $dto->user_gateway_id, + ]); + + $response = $this->fib_client->createPaymentService(); + $fib_response_dto = FIBPaymentCreateDTO::fromResponse($response); + + $transaction = Transaction::query()->create([ + 'user_id' => $payment->user_id, + 'amount' => $payment->amount, + 'currency' => $payment->currency, + 'callback_url' => $payment->callback_url, +// 'description' => '', +// 'expires_in' => '', + 'payment_id' => $payment->id, + 'payment_track_id' => $payment->track_id, + 'user_gateway_id' => $payment->user_gateway_id, +// 'status' => '', + ]); + + $fib_transactions = FibTransaction::query()->create([ + 'transaction_id' => $transaction->id, + 'qrcode' => $fib_response_dto->qrCode, + 'readable_code' => $fib_response_dto->readableCode, + 'personalAppLink' => $fib_response_dto->personalAppLink, + 'businessAppLink' => $fib_response_dto->businessAppLink, + 'corporateAppLink' => $fib_response_dto->corporateAppLink, + 'paymentId' => $fib_response_dto->paymentId, + 'paymentMethod' => 'fib', + 'validUntil' => $fib_response_dto->validUntil, + 'status_id' => PaymentStatusEnum::Unpaid->value, + 'status_name' => PaymentStatusEnum::Unpaid->name, + ]); + return $payment; + } catch (\Exception $exception) { + throw new \Exception($exception->getMessage()); + } } } diff --git a/backend/app/User/Controllers/Payment/DTO/PaymentCreateDTO.php b/backend/app/User/Controllers/Payment/DTO/PaymentCreateDTO.php deleted file mode 100644 index 30725e34..00000000 --- a/backend/app/User/Controllers/Payment/DTO/PaymentCreateDTO.php +++ /dev/null @@ -1,58 +0,0 @@ -username = $data['username'] ?? null; - $this->user_id = $data['user_id'] ?? null; - $this->track_id = $data['track_id'] ?? null; - $this->amount = $data['amount'] ?? null; - $this->currency = $data['currency'] ?? null; - $this->callback_url = $data['callback_url'] ?? null; - $this->expires_at = $data['expires_at'] ?? null; - $this->status_id = $data['status_id'] ?? null; - $this->status_name = $data['status_name'] ?? null; - $this->ip = $data['ip'] ?? null; - $this->user_gateway_id = $data['user_gateway_id'] ?? null; - $this->created_at = $data['created_at'] ?? null; - $this->updated_at = $data['updated_at'] ?? null; - } - - public static function fromRequest(Request $request): PaymentCreateDTO - { - return new self([ - 'username' => $request->username ?? null, - 'track_id' => $request->track_id ?? null, - 'amount' => $request->amount ?? null, - 'currency' => $request->currency ?? null, - 'callback_url' => $request->callback_url ?? null, - 'expires_at' => $request->expires_at ?? null, - 'status_id' => $request->status_id ?? null, - 'status_name' => $request->status_name ?? null, - 'ip' => $request->ip ?? null, - 'user_gateway_id' => $request->user_gateway_id ?? null, - 'created_at' => $request->created_at ?? null, - 'updated_at' => $request->updated_at ?? null, - ]); - } -} diff --git a/backend/app/User/Controllers/Payment/PaymentController.php b/backend/app/User/Controllers/Payment/PaymentController.php index 4ec9251b..69e7cd97 100644 --- a/backend/app/User/Controllers/Payment/PaymentController.php +++ b/backend/app/User/Controllers/Payment/PaymentController.php @@ -3,9 +3,10 @@ namespace App\User\Controllers\Payment; use App\Http\Controllers\Controller; +use App\Services\Payments\DTO\PaymentCreateDTO; use App\Services\Payments\PaymentService; use App\Traits\ApiResponse; -use App\User\Controllers\Payment\DTO\PaymentCreateDTO; +use App\User\Requests\Payment\FIB\StoreRequest; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; @@ -21,7 +22,7 @@ class PaymentController extends Controller $this->payment_service = $payment_service; } - public function createPayment(Request $request): JsonResponse + public function createPayment(StoreRequest $request): JsonResponse { try { $dto = PaymentCreateDTO::fromRequest($request); diff --git a/backend/app/User/Requests/Payment/FIB/StoreRequest.php b/backend/app/User/Requests/Payment/FIB/StoreRequest.php new file mode 100644 index 00000000..7ef61312 --- /dev/null +++ b/backend/app/User/Requests/Payment/FIB/StoreRequest.php @@ -0,0 +1,36 @@ +user()->kyc_status; + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules(): array + { + return [ + 'username' => ['required'], + 'track_id' => ['required'], + 'amount' => ['required'], + 'currency' => ['required'], + 'callback_url' => ['required'], + 'user_gateway_id' => ['required'], + ]; + } +} diff --git a/backend/database/migrations/2026_05_18_095610_create_transactions_table.php b/backend/database/migrations/2026_05_18_095610_create_transactions_table.php index 208f6f8e..0ffccd66 100644 --- a/backend/database/migrations/2026_05_18_095610_create_transactions_table.php +++ b/backend/database/migrations/2026_05_18_095610_create_transactions_table.php @@ -4,8 +4,7 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -return new class extends Migration -{ +return new class extends Migration { /** * Run the migrations. */ @@ -14,15 +13,15 @@ return new class extends Migration Schema::create('transactions', function (Blueprint $table) { $table->id(); $table->foreignId('user_id')->constrained('users'); - $table->string('amount'); - $table->string('currency'); - $table->string('callback_url'); - $table->string('description'); - $table->dateTime('expires_in'); + $table->string('amount')->nullable(); + $table->string('currency')->nullable(); + $table->string('callback_url')->nullable(); + $table->string('description')->nullable(); + $table->dateTime('expires_in')->nullable(); $table->foreignId('payment_id')->constrained('payments'); - $table->string('payment_track_id'); + $table->string('payment_track_id')->nullable(); $table->foreignId('user_gateway_id')->constrained('user_gateways'); - $table->integer('status'); + $table->integer('status')->nullable(); $table->timestamps(); }); } diff --git a/backend/database/migrations/2026_05_18_104524_create_fib_transactions_table.php b/backend/database/migrations/2026_05_18_104524_create_fib_transactions_table.php index 3e914953..2b346264 100644 --- a/backend/database/migrations/2026_05_18_104524_create_fib_transactions_table.php +++ b/backend/database/migrations/2026_05_18_104524_create_fib_transactions_table.php @@ -4,8 +4,7 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -return new class extends Migration -{ +return new class extends Migration { /** * Run the migrations. */ @@ -14,23 +13,23 @@ return new class extends Migration Schema::create('fib_transactions', function (Blueprint $table) { $table->id(); $table->foreignId('transaction_id')->constrained('transactions'); - $table->text('qrcode'); - $table->string('readable_code'); - $table->string('personalAppLink'); - $table->string('businessAppLink'); - $table->string('corporateAppLink'); - $table->string('paymentId'); - $table->string('paymentMethod'); - $table->timestamp('validUntil'); + $table->text('qrcode')->nullable(); + $table->string('readable_code')->nullable(); + $table->string('personalAppLink')->nullable(); + $table->string('businessAppLink')->nullable(); + $table->string('corporateAppLink')->nullable(); + $table->string('paymentId')->nullable(); + $table->string('paymentMethod')->nullable(); + $table->timestamp('validUntil')->nullable(); $table->foreignId('status_id')->constrained('fib_transaction_statuses'); - $table->string('status_name'); - $table->timestamp('paidAt'); - $table->string('paidAmount'); - $table->string('paidCurrency'); - $table->string('decliningReason'); - $table->timestamp('declinedAt'); - $table->string('paidByName'); - $table->string('paidByIban'); + $table->string('status_name')->nullable(); + $table->timestamp('paidAt')->nullable(); + $table->string('paidAmount')->nullable(); + $table->string('paidCurrency')->nullable(); + $table->string('decliningReason')->nullable(); + $table->timestamp('declinedAt')->nullable(); + $table->string('paidByName')->nullable(); + $table->string('paidByIban')->nullable(); $table->timestamps(); }); } diff --git a/backend/database/seeders/FibTransactionStatusSeeder.php b/backend/database/seeders/FibTransactionStatusSeeder.php index 0949ff45..f634b061 100644 --- a/backend/database/seeders/FibTransactionStatusSeeder.php +++ b/backend/database/seeders/FibTransactionStatusSeeder.php @@ -4,6 +4,7 @@ namespace Database\Seeders; use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; +use Illuminate\Support\Facades\DB; class FibTransactionStatusSeeder extends Seeder { @@ -12,6 +13,12 @@ class FibTransactionStatusSeeder extends Seeder */ public function run(): void { - // + DB::table('fib_transaction_statuses')->insert([ + ['id' => 0, 'name' => 'unpaid', 'created_at' => now(), 'updated_at' => now(),], + ['id' => 1, 'name' => 'cancelled', 'created_at' => now(), 'updated_at' => now(),], + ['id' => 2, 'name' => 'expired', 'created_at' => now(), 'updated_at' => now(),], + ['id' => 3, 'name' => 'paid', 'created_at' => now(), 'updated_at' => now(),], + ['id' => 4, 'name' => 'refunded', 'created_at' => now(), 'updated_at' => now(),], + ]); } } diff --git a/backend/routes/api.php b/backend/routes/api.php index a511350e..5eaa6efa 100755 --- a/backend/routes/api.php +++ b/backend/routes/api.php @@ -10,8 +10,10 @@ Route::get('/user', function (Request $request) { })->middleware('auth:api'); Route::prefix('v1')->group(function () { - Route::prefix('payment')->group(function () { - Route::get('/create_payment', [PaymentController::class, 'createPayment']); - }); + Route::prefix('payment') +// ->middleware('auth:api') + ->group(function () { + Route::post('/create_payment', [PaymentController::class, 'createPayment'])->name('create_payment'); + }); }); -- 2.49.1 From 5e48777e1c4c1d803094af63d08b78b829936b13 Mon Sep 17 00:00:00 2001 From: faezehzafarbakhsh Date: Sat, 23 May 2026 13:45:05 +0330 Subject: [PATCH 52/61] create list for city and province --- .../app/User/Controllers/CityController.php | 25 +++++++++++++++++++ .../User/Controllers/ProvinceController.php | 24 ++++++++++++++++++ backend/routes/web.php | 15 +++++++++++ 3 files changed, 64 insertions(+) create mode 100755 backend/app/User/Controllers/CityController.php create mode 100755 backend/app/User/Controllers/ProvinceController.php diff --git a/backend/app/User/Controllers/CityController.php b/backend/app/User/Controllers/CityController.php new file mode 100755 index 00000000..eed3d80f --- /dev/null +++ b/backend/app/User/Controllers/CityController.php @@ -0,0 +1,25 @@ +successResponse($city); + } +} diff --git a/backend/app/User/Controllers/ProvinceController.php b/backend/app/User/Controllers/ProvinceController.php new file mode 100755 index 00000000..0d5e1500 --- /dev/null +++ b/backend/app/User/Controllers/ProvinceController.php @@ -0,0 +1,24 @@ +successResponse($province); + } +} diff --git a/backend/routes/web.php b/backend/routes/web.php index 28784019..07967c89 100755 --- a/backend/routes/web.php +++ b/backend/routes/web.php @@ -5,7 +5,9 @@ use App\Admin\Controllers\PermissionManagementController; use App\Admin\Controllers\RoleManagementController; use App\Admin\Controllers\UserManagementController; use App\User\Controllers\AuthController; +use App\User\Controllers\CityController; use App\User\Controllers\ProfileController; +use App\User\Controllers\ProvinceController; use Illuminate\Support\Facades\Route; Route::prefix('auth') @@ -26,3 +28,16 @@ Route::prefix('profile') Route::post('edit', 'edit')->name('edit'); Route::post('change_password', 'changePassword')->name('changePassword'); }); +Route::prefix('province') + ->name('province.') + ->controller(ProvinceController::class) + ->group(function () { + Route::get('list', 'list')->name('list'); + }); + +Route::prefix('city') + ->name('city.') + ->controller(CityController::class) + ->group(function () { + Route::get('list', 'list')->name('list'); + }); -- 2.49.1 From 8554f4cc553d66416f0c783cf48f93c47a4e0f05 Mon Sep 17 00:00:00 2001 From: faezehzafarbakhsh Date: Sat, 23 May 2026 13:49:09 +0330 Subject: [PATCH 53/61] create list for city and province --- backend/app/User/Controllers/CityController.php | 7 ------- backend/app/User/Controllers/ProvinceController.php | 6 ------ 2 files changed, 13 deletions(-) diff --git a/backend/app/User/Controllers/CityController.php b/backend/app/User/Controllers/CityController.php index eed3d80f..eecd374c 100755 --- a/backend/app/User/Controllers/CityController.php +++ b/backend/app/User/Controllers/CityController.php @@ -4,15 +4,8 @@ namespace App\User\Controllers; use App\Http\Controllers\Controller; use App\Models\City; -use App\Models\Province; -use App\Models\User; use App\Traits\ApiResponse; -use App\User\Requests\Auth\LoginRequest; -use App\User\Requests\Auth\RegisterRequest; use Illuminate\Http\JsonResponse; -use Illuminate\Http\Request; -use Illuminate\Support\Facades\Auth; -use Illuminate\Support\Facades\Hash; class CityController extends Controller { diff --git a/backend/app/User/Controllers/ProvinceController.php b/backend/app/User/Controllers/ProvinceController.php index 0d5e1500..bc9d6b8c 100755 --- a/backend/app/User/Controllers/ProvinceController.php +++ b/backend/app/User/Controllers/ProvinceController.php @@ -4,14 +4,8 @@ namespace App\User\Controllers; use App\Http\Controllers\Controller; use App\Models\Province; -use App\Models\User; use App\Traits\ApiResponse; -use App\User\Requests\Auth\LoginRequest; -use App\User\Requests\Auth\RegisterRequest; use Illuminate\Http\JsonResponse; -use Illuminate\Http\Request; -use Illuminate\Support\Facades\Auth; -use Illuminate\Support\Facades\Hash; class ProvinceController extends Controller { -- 2.49.1 From 9ad3d6cea0d3845df22cdf87194e7b440c072a5f Mon Sep 17 00:00:00 2001 From: faezehzafarbakhsh Date: Sat, 23 May 2026 14:03:10 +0330 Subject: [PATCH 54/61] create show for city and province --- backend/app/User/Controllers/CityController.php | 17 ++++++++++++++++- .../app/User/Controllers/ProvinceController.php | 17 ++++++++++++++++- backend/routes/web.php | 3 +++ 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/backend/app/User/Controllers/CityController.php b/backend/app/User/Controllers/CityController.php index eecd374c..25a796f4 100755 --- a/backend/app/User/Controllers/CityController.php +++ b/backend/app/User/Controllers/CityController.php @@ -2,16 +2,31 @@ namespace App\User\Controllers; +use App\Facades\DataTable\DataTableFacade; use App\Http\Controllers\Controller; use App\Models\City; use App\Traits\ApiResponse; use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; class CityController extends Controller { use ApiResponse; - public function list(City $city): JsonResponse + public function list(Request $request): JsonResponse + { + $data = DataTableFacade::run( + City::query(), + $request, + allowedFilters: ['*'], + allowedSortings: ['*'], + allowedSelects: ['id', 'name'] + ); + + return response()->json($data); + } + + public function show(City $city): JsonResponse { return $this->successResponse($city); } diff --git a/backend/app/User/Controllers/ProvinceController.php b/backend/app/User/Controllers/ProvinceController.php index bc9d6b8c..cfe81efa 100755 --- a/backend/app/User/Controllers/ProvinceController.php +++ b/backend/app/User/Controllers/ProvinceController.php @@ -2,16 +2,31 @@ namespace App\User\Controllers; +use App\Facades\DataTable\DataTableFacade; use App\Http\Controllers\Controller; +use App\Models\City; use App\Models\Province; use App\Traits\ApiResponse; use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; class ProvinceController extends Controller { use ApiResponse; + public function list(Request $request): JsonResponse + { + $data = DataTableFacade::run( + Province::query(), + $request, + allowedFilters: ['*'], + allowedSortings: ['*'], + allowedSelects: ['id', 'name'] + ); - public function list(Province $province): JsonResponse + return response()->json($data); + } + + public function show(Province $province): JsonResponse { return $this->successResponse($province); } diff --git a/backend/routes/web.php b/backend/routes/web.php index 07967c89..3287c409 100755 --- a/backend/routes/web.php +++ b/backend/routes/web.php @@ -28,11 +28,13 @@ Route::prefix('profile') Route::post('edit', 'edit')->name('edit'); Route::post('change_password', 'changePassword')->name('changePassword'); }); + Route::prefix('province') ->name('province.') ->controller(ProvinceController::class) ->group(function () { Route::get('list', 'list')->name('list'); + Route::get('/{province}', 'show')->name('show'); }); Route::prefix('city') @@ -40,4 +42,5 @@ Route::prefix('city') ->controller(CityController::class) ->group(function () { Route::get('list', 'list')->name('list'); + Route::get('/{city}', 'show')->name('show'); }); -- 2.49.1 From d9f895c382aed143a66006ecc269459148c20e8d Mon Sep 17 00:00:00 2001 From: faezehzafarbakhsh Date: Sat, 23 May 2026 14:55:00 +0330 Subject: [PATCH 55/61] create seeder for city and province --- backend/database/seeders/CitySeeder.php | 122 +++++++++++++++++++- backend/database/seeders/DatabaseSeeder.php | 4 + backend/database/seeders/ProvinceSeeder.php | 22 +++- 3 files changed, 146 insertions(+), 2 deletions(-) diff --git a/backend/database/seeders/CitySeeder.php b/backend/database/seeders/CitySeeder.php index 670c9643..83f4ea9a 100755 --- a/backend/database/seeders/CitySeeder.php +++ b/backend/database/seeders/CitySeeder.php @@ -4,6 +4,7 @@ namespace Database\Seeders; use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; +use Illuminate\Support\Facades\DB; class CitySeeder extends Seeder { @@ -12,6 +13,125 @@ class CitySeeder extends Seeder */ public function run(): void { - // + DB::table('cities')->insert([ + // Baghdad + ['province_id' => 1, 'name' => 'Baghdad', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 1, 'name' => 'Sadr City', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 1, 'name' => 'Kadhimiya', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 1, 'name' => 'Adhamiyah', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 1, 'name' => 'Abu Ghraib', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 1, 'name' => 'Mahmudiya', 'created_at' => now(), 'updated_at' => now()], + + // Basra + ['province_id' => 2, 'name' => 'Basra', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 2, 'name' => 'Al-Zubair', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 2, 'name' => 'Umm Qasr', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 2, 'name' => 'Al-Qurnah', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 2, 'name' => 'Shatt Al-Arab', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 2, 'name' => 'Abu Al-Khaseeb', 'created_at' => now(), 'updated_at' => now()], + + // Nineveh + ['province_id' => 3, 'name' => 'Mosul', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 3, 'name' => 'Tal Afar', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 3, 'name' => 'Sinjar', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 3, 'name' => 'Al-Hamdaniya', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 3, 'name' => 'Bashiqa', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 3, 'name' => 'Qayyarah', 'created_at' => now(), 'updated_at' => now()], + + // Erbil + ['province_id' => 4, 'name' => 'Erbil', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 4, 'name' => 'Shaqlawa', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 4, 'name' => 'Soran', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 4, 'name' => 'Koya', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 4, 'name' => 'Makhmur', 'created_at' => now(), 'updated_at' => now()], + + // Sulaymaniyah + ['province_id' => 5, 'name' => 'Sulaymaniyah', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 5, 'name' => 'Halabja', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 5, 'name' => 'Ranya', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 5, 'name' => 'Kalar', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 5, 'name' => 'Chamchamal', 'created_at' => now(), 'updated_at' => now()], + + // Duhok + ['province_id' => 6, 'name' => 'Duhok', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 6, 'name' => 'Zakho', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 6, 'name' => 'Amedi', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 6, 'name' => 'Akre', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 6, 'name' => 'Semel', 'created_at' => now(), 'updated_at' => now()], + + // Kirkuk + ['province_id' => 7, 'name' => 'Kirkuk', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 7, 'name' => 'Hawija', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 7, 'name' => 'Dibis', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 7, 'name' => 'Daquq', 'created_at' => now(), 'updated_at' => now()], + + // Diyala + ['province_id' => 8, 'name' => 'Baqubah', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 8, 'name' => 'Khanaqin', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 8, 'name' => 'Muqdadiyah', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 8, 'name' => 'Balad Ruz', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 8, 'name' => 'Jalawla', 'created_at' => now(), 'updated_at' => now()], + + // Anbar + ['province_id' => 9, 'name' => 'Ramadi', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 9, 'name' => 'Fallujah', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 9, 'name' => 'Haditha', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 9, 'name' => 'Hit', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 9, 'name' => 'Al-Qaim', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 9, 'name' => 'Rutba', 'created_at' => now(), 'updated_at' => now()], + + // Salah al-Din + ['province_id' => 10, 'name' => 'Tikrit', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 10, 'name' => 'Samarra', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 10, 'name' => 'Balad', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 10, 'name' => 'Baiji', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 10, 'name' => 'Tuz Khurmatu', 'created_at' => now(), 'updated_at' => now()], + + // Karbala + ['province_id' => 11, 'name' => 'Karbala', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 11, 'name' => 'Al-Hindiya', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 11, 'name' => 'Ain Al-Tamur', 'created_at' => now(), 'updated_at' => now()], + + // Najaf + ['province_id' => 12, 'name' => 'Najaf', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 12, 'name' => 'Kufa', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 12, 'name' => 'Al-Manathera', 'created_at' => now(), 'updated_at' => now()], + + // Babil + ['province_id' => 13, 'name' => 'Hillah', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 13, 'name' => 'Al-Mahawil', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 13, 'name' => 'Al-Musayyib', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 13, 'name' => 'Hashimiya', 'created_at' => now(), 'updated_at' => now()], + + // Wasit + ['province_id' => 14, 'name' => 'Kut', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 14, 'name' => 'Al-Hayy', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 14, 'name' => 'Al-Numaniyah', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 14, 'name' => 'Badra', 'created_at' => now(), 'updated_at' => now()], + + // Maysan + ['province_id' => 15, 'name' => 'Amarah', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 15, 'name' => 'Ali Al-Sharqi', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 15, 'name' => 'Al-Majarr Al-Kabir', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 15, 'name' => 'Qalat Saleh', 'created_at' => now(), 'updated_at' => now()], + + // Dhi Qar + ['province_id' => 16, 'name' => 'Nasiriyah', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 16, 'name' => 'Al-Shatrah', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 16, 'name' => 'Suq Al-Shuyukh', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 16, 'name' => 'Rifai', 'created_at' => now(), 'updated_at' => now()], + + // Muthanna + ['province_id' => 17, 'name' => 'Samawah', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 17, 'name' => 'Rumaitha', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 17, 'name' => 'Al-Khidhir', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 17, 'name' => 'Salman', 'created_at' => now(), 'updated_at' => now()], + + // Al-Qadisiyyah + ['province_id' => 18, 'name' => 'Diwaniyah', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 18, 'name' => 'Afak', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 18, 'name' => 'Al-Shamiya', 'created_at' => now(), 'updated_at' => now()], + ['province_id' => 18, 'name' => 'Hamza', 'created_at' => now(), 'updated_at' => now()], + ]); } } diff --git a/backend/database/seeders/DatabaseSeeder.php b/backend/database/seeders/DatabaseSeeder.php index 90d0b1b1..d47e5cb2 100755 --- a/backend/database/seeders/DatabaseSeeder.php +++ b/backend/database/seeders/DatabaseSeeder.php @@ -22,5 +22,9 @@ class DatabaseSeeder extends Seeder 'email' => 'test@example.com', 'password' => 'password123@', ]); + $this->call([ + ProvinceSeeder::class, + CitySeeder::class, + ]); } } diff --git a/backend/database/seeders/ProvinceSeeder.php b/backend/database/seeders/ProvinceSeeder.php index 56127fcf..325597d8 100755 --- a/backend/database/seeders/ProvinceSeeder.php +++ b/backend/database/seeders/ProvinceSeeder.php @@ -4,6 +4,7 @@ namespace Database\Seeders; use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; +use Illuminate\Support\Facades\DB; class ProvinceSeeder extends Seeder { @@ -12,6 +13,25 @@ class ProvinceSeeder extends Seeder */ public function run(): void { - // + DB::table('provinces')->insert([ + ['id' => 1, 'name' => 'Baghdad', 'created_at' => now(), 'updated_at' => now()], + ['id' => 2, 'name' => 'Basra', 'created_at' => now(), 'updated_at' => now()], + ['id' => 3, 'name' => 'Nineveh', 'created_at' => now(), 'updated_at' => now()], + ['id' => 4, 'name' => 'Erbil', 'created_at' => now(), 'updated_at' => now()], + ['id' => 5, 'name' => 'Sulaymaniyah', 'created_at' => now(), 'updated_at' => now()], + ['id' => 6, 'name' => 'Duhok', 'created_at' => now(), 'updated_at' => now()], + ['id' => 7, 'name' => 'Kirkuk', 'created_at' => now(), 'updated_at' => now()], + ['id' => 8, 'name' => 'Diyala', 'created_at' => now(), 'updated_at' => now()], + ['id' => 9, 'name' => 'Anbar', 'created_at' => now(), 'updated_at' => now()], + ['id' => 10, 'name' => 'Salah al-Din', 'created_at' => now(), 'updated_at' => now()], + ['id' => 11, 'name' => 'Karbala', 'created_at' => now(), 'updated_at' => now()], + ['id' => 12, 'name' => 'Najaf', 'created_at' => now(), 'updated_at' => now()], + ['id' => 13, 'name' => 'Babil', 'created_at' => now(), 'updated_at' => now()], + ['id' => 14, 'name' => 'Wasit', 'created_at' => now(), 'updated_at' => now()], + ['id' => 15, 'name' => 'Maysan', 'created_at' => now(), 'updated_at' => now()], + ['id' => 16, 'name' => 'Dhi Qar', 'created_at' => now(), 'updated_at' => now()], + ['id' => 17, 'name' => 'Muthanna', 'created_at' => now(), 'updated_at' => now()], + ['id' => 18, 'name' => 'Al-Qadisiyyah', 'created_at' => now(), 'updated_at' => now()], + ]); } } -- 2.49.1 From 5c757e18233cf035d8077f1870db0494576c2cd1 Mon Sep 17 00:00:00 2001 From: faezehzafarbakhsh Date: Sat, 23 May 2026 15:13:12 +0330 Subject: [PATCH 56/61] create seeder for city and province --- .../app/User/Controllers/CityController.php | 19 ++---------------- .../User/Controllers/ProvinceController.php | 20 ++----------------- backend/routes/web.php | 2 -- 3 files changed, 4 insertions(+), 37 deletions(-) diff --git a/backend/app/User/Controllers/CityController.php b/backend/app/User/Controllers/CityController.php index 25a796f4..8ac7e042 100755 --- a/backend/app/User/Controllers/CityController.php +++ b/backend/app/User/Controllers/CityController.php @@ -2,32 +2,17 @@ namespace App\User\Controllers; -use App\Facades\DataTable\DataTableFacade; use App\Http\Controllers\Controller; use App\Models\City; use App\Traits\ApiResponse; use Illuminate\Http\JsonResponse; -use Illuminate\Http\Request; class CityController extends Controller { use ApiResponse; - public function list(Request $request): JsonResponse + public function list(): JsonResponse { - $data = DataTableFacade::run( - City::query(), - $request, - allowedFilters: ['*'], - allowedSortings: ['*'], - allowedSelects: ['id', 'name'] - ); - - return response()->json($data); - } - - public function show(City $city): JsonResponse - { - return $this->successResponse($city); + return response()->json(City::all(['id', 'name'])); } } diff --git a/backend/app/User/Controllers/ProvinceController.php b/backend/app/User/Controllers/ProvinceController.php index cfe81efa..0d340730 100755 --- a/backend/app/User/Controllers/ProvinceController.php +++ b/backend/app/User/Controllers/ProvinceController.php @@ -2,32 +2,16 @@ namespace App\User\Controllers; -use App\Facades\DataTable\DataTableFacade; use App\Http\Controllers\Controller; -use App\Models\City; use App\Models\Province; use App\Traits\ApiResponse; use Illuminate\Http\JsonResponse; -use Illuminate\Http\Request; class ProvinceController extends Controller { use ApiResponse; - public function list(Request $request): JsonResponse + public function list(): JsonResponse { - $data = DataTableFacade::run( - Province::query(), - $request, - allowedFilters: ['*'], - allowedSortings: ['*'], - allowedSelects: ['id', 'name'] - ); - - return response()->json($data); - } - - public function show(Province $province): JsonResponse - { - return $this->successResponse($province); + return response()->json(Province::all(['id', 'name'])); } } diff --git a/backend/routes/web.php b/backend/routes/web.php index 3287c409..cac32527 100755 --- a/backend/routes/web.php +++ b/backend/routes/web.php @@ -34,7 +34,6 @@ Route::prefix('province') ->controller(ProvinceController::class) ->group(function () { Route::get('list', 'list')->name('list'); - Route::get('/{province}', 'show')->name('show'); }); Route::prefix('city') @@ -42,5 +41,4 @@ Route::prefix('city') ->controller(CityController::class) ->group(function () { Route::get('list', 'list')->name('list'); - Route::get('/{city}', 'show')->name('show'); }); -- 2.49.1 From 8be89d350f3f2ad1c1e366d2a118906b7bde5725 Mon Sep 17 00:00:00 2001 From: faezehzafarbakhsh Date: Sat, 23 May 2026 16:36:40 +0330 Subject: [PATCH 57/61] change show province --- backend/app/User/Controllers/CityController.php | 10 ++++++++-- backend/app/User/Controllers/ProvinceController.php | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/backend/app/User/Controllers/CityController.php b/backend/app/User/Controllers/CityController.php index 8ac7e042..c14f5677 100755 --- a/backend/app/User/Controllers/CityController.php +++ b/backend/app/User/Controllers/CityController.php @@ -4,15 +4,21 @@ namespace App\User\Controllers; use App\Http\Controllers\Controller; use App\Models\City; +use App\Models\Province; use App\Traits\ApiResponse; use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; class CityController extends Controller { use ApiResponse; - public function list(): JsonResponse + public function list(Request $request): JsonResponse { - return response()->json(City::all(['id', 'name'])); + $cities = City::query() + ->where('province_id', '=', $request->query('province_id')) + ->get(['id', 'name']); + + return $this->successResponse($cities); } } diff --git a/backend/app/User/Controllers/ProvinceController.php b/backend/app/User/Controllers/ProvinceController.php index 0d340730..d73254d3 100755 --- a/backend/app/User/Controllers/ProvinceController.php +++ b/backend/app/User/Controllers/ProvinceController.php @@ -12,6 +12,6 @@ class ProvinceController extends Controller use ApiResponse; public function list(): JsonResponse { - return response()->json(Province::all(['id', 'name'])); + return $this->successResponse(Province::all(['id', 'name'])); } } -- 2.49.1 From 1b5d339a53861339a9ae0e0812aa3daad49606ea Mon Sep 17 00:00:00 2001 From: faezehzafarbakhsh Date: Mon, 25 May 2026 15:00:44 +0330 Subject: [PATCH 58/61] fix login code in3 point --- backend/app/Admin/Controllers/AuthController.php | 16 +--------------- backend/app/Models/Expert.php | 8 +++++--- backend/config/auth.php | 1 + backend/routes/expert.php | 1 - 4 files changed, 7 insertions(+), 19 deletions(-) diff --git a/backend/app/Admin/Controllers/AuthController.php b/backend/app/Admin/Controllers/AuthController.php index 4ea69b0a..9bd88920 100755 --- a/backend/app/Admin/Controllers/AuthController.php +++ b/backend/app/Admin/Controllers/AuthController.php @@ -16,19 +16,6 @@ class AuthController extends Controller { use ApiResponse; - public function register(RegisterRequest $request): JsonResponse - { - Expert::query()->create([ - 'username' => $request->username, - 'email' => $request->email, - 'password' => Hash::make($request->password), - ]); - - $request->session()->regenerate(); - - return $this->successResponse(); - } - public function login(LoginRequest $request): JsonResponse { $credentials = ['password' => $request->password]; @@ -39,8 +26,7 @@ class AuthController extends Controller $credentials['username'] = $request->login; } - if (Auth::attempt($credentials)) - { + if (Auth::guard('expert')->attempt($credentials)) { $request->session()->regenerate(); return $this->successResponse(); diff --git a/backend/app/Models/Expert.php b/backend/app/Models/Expert.php index 8003184b..ca4912a5 100755 --- a/backend/app/Models/Expert.php +++ b/backend/app/Models/Expert.php @@ -5,17 +5,19 @@ namespace App\Models; use Database\Factories\ExpertFactory; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use Illuminate\Notifications\Notifiable; +use Illuminate\Foundation\Auth\User as Authenticatable; use Spatie\Permission\Traits\HasRoles; /** * @method roles() * @method permissions() */ -class Expert extends Model +class Expert extends Authenticatable { /** @use HasFactory */ - use HasFactory; - use HasRoles; + + use Notifiable,HasFactory,HasRoles; protected $guarded = ['id']; protected string $guard_name = 'web'; diff --git a/backend/config/auth.php b/backend/config/auth.php index a968e457..63cce897 100755 --- a/backend/config/auth.php +++ b/backend/config/auth.php @@ -1,5 +1,6 @@ name('auth.') ->controller(AuthController::class) ->group(function () { - Route::post('register', 'register')->name('register'); Route::post('login', 'login')->name('login'); Route::post('logout', 'logout')->name('logout'); }); -- 2.49.1 From 26a0f79a6dd8fb8a06b8ba5dc9e975a76b1941f8 Mon Sep 17 00:00:00 2001 From: faezehzafarbakhsh Date: Mon, 25 May 2026 16:12:14 +0330 Subject: [PATCH 59/61] fix enum for gateway managment --- backend/app/Enums/BusinessTypeEnum.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/app/Enums/BusinessTypeEnum.php b/backend/app/Enums/BusinessTypeEnum.php index 232cc49f..5bef3f00 100644 --- a/backend/app/Enums/BusinessTypeEnum.php +++ b/backend/app/Enums/BusinessTypeEnum.php @@ -41,7 +41,7 @@ enum BusinessTypeEnum: int }; } - public function name($id): string + public static function name($id): string { $mapArray = [ 1 => 'medical', -- 2.49.1 From 5040299efc74ccddb20c5d2b2c6ccb662688fa28 Mon Sep 17 00:00:00 2001 From: faezehzafarbakhsh Date: Mon, 25 May 2026 16:18:08 +0330 Subject: [PATCH 60/61] fix user resource and profil --- backend/app/User/Controllers/ProfileController.php | 2 +- backend/app/User/Resources/UserResource.php | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/backend/app/User/Controllers/ProfileController.php b/backend/app/User/Controllers/ProfileController.php index f8fb83e2..3a055d91 100755 --- a/backend/app/User/Controllers/ProfileController.php +++ b/backend/app/User/Controllers/ProfileController.php @@ -46,7 +46,7 @@ class ProfileController extends Controller $user->update([ 'first_name' => $request->first_name, 'last_name' => $request->last_name, - 'national_id' => $request->national_id, + 'national_id' => $user->kyc_status == 0 ? $request->national_id : $user->national_id, 'address' => $request->address, 'postal_code' => $request->postal_code, 'phone_number' => $request->phone_number, diff --git a/backend/app/User/Resources/UserResource.php b/backend/app/User/Resources/UserResource.php index d5df7636..9f98d7f8 100755 --- a/backend/app/User/Resources/UserResource.php +++ b/backend/app/User/Resources/UserResource.php @@ -24,12 +24,13 @@ class UserResource extends JsonResource 'address' => $this->address, 'postal_code' => $this->postal_code, 'phone_number' => $this->phone_number, - 'province_id' => $this->provonce_id, + 'province_id' => $this->province_id, 'province_name' => $this->province_name, 'city_id' => $this->city_id, 'city_name' => $this->city_name, 'birth_date' => $this->birth_date, 'kyc_status' => $this->kyc_status, + 'authority_level' => $this->authority_level, ]; } } -- 2.49.1 From 99d24c38c7b9c5a8cd00cccc83155832fb0f1c5f Mon Sep 17 00:00:00 2001 From: faezehzafarbakhsh Date: Mon, 1 Jun 2026 13:43:16 +0330 Subject: [PATCH 61/61] create forgote password --- .../ForgotPasswordOtpController.php | 89 +++++++++++++++ backend/app/Models/PasswordResetOtp.php | 10 ++ .../ResetPasswordOtpNotification.php | 32 ++++++ .../Services/Auth/PasswordResetOtpService.php | 103 ++++++++++++++++++ .../ForgotPasswordOtpController.php | 89 +++++++++++++++ ...62350_create_password_reset_otps_table.php | 30 +++++ 6 files changed, 353 insertions(+) create mode 100644 backend/app/Admin/Controllers/ForgotPasswordOtpController.php create mode 100644 backend/app/Models/PasswordResetOtp.php create mode 100644 backend/app/Notifications/ResetPasswordOtpNotification.php create mode 100644 backend/app/Services/Auth/PasswordResetOtpService.php create mode 100644 backend/app/User/Controllers/ForgotPasswordOtpController.php create mode 100755 backend/database/migrations/2026_06_01_062350_create_password_reset_otps_table.php diff --git a/backend/app/Admin/Controllers/ForgotPasswordOtpController.php b/backend/app/Admin/Controllers/ForgotPasswordOtpController.php new file mode 100644 index 00000000..cdd62a45 --- /dev/null +++ b/backend/app/Admin/Controllers/ForgotPasswordOtpController.php @@ -0,0 +1,89 @@ +validate([ + 'email' => ['required', 'email', 'exists:users,email'], + ]); + + $email = $validated['email']; + + $key = 'user-forgot-password-otp:' . $email . ':' . $request->ip(); + + if (RateLimiter::tooManyAttempts($key, 3)) { + throw ValidationException::withMessages([ + 'email' => ['Too many OTP requests. Please try again later.'], + ]); + } + + RateLimiter::hit($key, 600); + + $user = User::query()->where('email', $email)->firstOrFail(); + + $this->otpService->sendOtp($user, 'user'); + + return response()->json([ + 'message' => 'OTP sent to your email.', + ]); + } + + public function verifyOtp(Request $request): JsonResponse + { + $validated = $request->validate([ + 'email' => ['required', 'email', 'exists:users,email'], + 'otp' => ['required', 'digits:6'], + ]); + + $this->otpService->verifyOtp( + email: $validated['email'], + otp: $validated['otp'], + guard: 'user' + ); + + return response()->json([ + 'message' => 'OTP verified successfully.', + ]); + } + + public function resetPassword(Request $request): JsonResponse + { + $validated = $request->validate([ + 'email' => ['required', 'email', 'exists:users,email'], + 'password' => ['required', 'confirmed',], + ]); + + $record = $this->otpService->ensureVerified( + email: $validated['email'], + guard: 'user' + ); + + $user = User::query()->where('email', $validated['email'])->firstOrFail(); + + $user->update([ + 'password' => Hash::make($validated['password']), + ]); + + $record->delete(); + + return response()->json([ + 'message' => 'User password reset successfully.', + ]); + } +} diff --git a/backend/app/Models/PasswordResetOtp.php b/backend/app/Models/PasswordResetOtp.php new file mode 100644 index 00000000..0be42dab --- /dev/null +++ b/backend/app/Models/PasswordResetOtp.php @@ -0,0 +1,10 @@ +subject('Reset Password OTP') + ->greeting('Hello!') + ->line('Your password reset code is:') + ->line($this->otp) + ->line('This code will expire in 10 minutes.') + ->line('If you did not request password reset, ignore this email.'); + } +} diff --git a/backend/app/Services/Auth/PasswordResetOtpService.php b/backend/app/Services/Auth/PasswordResetOtpService.php new file mode 100644 index 00000000..aa73ecdd --- /dev/null +++ b/backend/app/Services/Auth/PasswordResetOtpService.php @@ -0,0 +1,103 @@ +where('email', $user->email) + ->where('guard', $guard) + ->delete(); + + PasswordResetOtp::query()->create([ + 'email' => $user->email, + 'guard' => $guard, + 'otp' => Hash::make($otp), + 'expires_at' => now()->addMinutes(10), + ]); + + $user->notify(new ResetPasswordOtpNotification($otp)); + } + + public function verifyOtp(string $email, string $otp, string $guard = 'user'): void + { + $record = PasswordResetOtp::query()->where('email', $email) + ->where('guard', $guard) + ->latest() + ->first(); + + if (! $record) { + throw ValidationException::withMessages([ + 'otp' => ['Invalid OTP.'], + ]); + } + + if ($record->isExpired()) { + $record->delete(); + + throw ValidationException::withMessages([ + 'otp' => ['OTP has expired.'], + ]); + } + + if ($record->attempts >= 5) { + $record->delete(); + + throw ValidationException::withMessages([ + 'otp' => ['Too many wrong attempts. Please request a new OTP.'], + ]); + } + + if (! Hash::check($otp, $record->otp)) { + $record->increment('attempts'); + + throw ValidationException::withMessages([ + 'otp' => ['Invalid OTP.'], + ]); + } + + $record->update([ + 'verified_at' => now(), + ]); + } + + public function ensureVerified(string $email, string $guard = 'user'): PasswordResetOtp + { + $record = PasswordResetOtp::query()->where('email', $email) + ->where('guard', $guard) + ->latest() + ->first(); + + if (! $record || ! $record->isVerified()) { + throw ValidationException::withMessages([ + 'otp' => ['Please verify OTP first.'], + ]); + } + + if ($record->isExpired()) { + $record->delete(); + + throw ValidationException::withMessages([ + 'otp' => ['OTP has expired.'], + ]); + } + + return $record; + } + + public function deleteOtp(string $email, string $guard = 'user'): void + { + PasswordResetOtp::query()->where('email', $email) + ->where('guard', $guard) + ->delete(); + } +} diff --git a/backend/app/User/Controllers/ForgotPasswordOtpController.php b/backend/app/User/Controllers/ForgotPasswordOtpController.php new file mode 100644 index 00000000..51e3889b --- /dev/null +++ b/backend/app/User/Controllers/ForgotPasswordOtpController.php @@ -0,0 +1,89 @@ +validate([ + 'email' => ['required', 'email', 'exists:experts,email'], + ]); + + $email = $validated['email']; + + $key = 'expert-forgot-password-otp:' . $email . ':' . $request->ip(); + + if (RateLimiter::tooManyAttempts($key, 3)) { + throw ValidationException::withMessages([ + 'email' => ['Too many OTP requests. Please try again later.'], + ]); + } + + RateLimiter::hit($key, 600); + + $expert = Expert::query()->where('email', $email)->firstOrFail(); + + $this->otpService->sendOtp($expert, 'expert'); + + return response()->json([ + 'message' => 'OTP sent to your email.', + ]); + } + + public function verifyOtp(Request $request): JsonResponse + { + $validated = $request->validate([ + 'email' => ['required', 'email', 'exists:experts,email'], + 'otp' => ['required', 'digits:6'], + ]); + + $this->otpService->verifyOtp( + email: $validated['email'], + otp: $validated['otp'], + guard: 'expert' + ); + + return response()->json([ + 'message' => 'OTP verified successfully.', + ]); + } + + public function resetPassword(Request $request): JsonResponse + { + $validated = $request->validate([ + 'email' => ['required', 'email', 'exists:experts,email'], + 'password' => ['required', 'confirmed'], + ]); + + $record = $this->otpService->ensureVerified( + email: $validated['email'], + guard: 'expert' + ); + + $expert = Expert::query()->where('email', $validated['email'])->firstOrFail(); + + $expert->update([ + 'password' => Hash::make($validated['password']), + ]); + + $record->delete(); + + return response()->json([ + 'message' => 'Expert password reset successfully.', + ]); + } +} diff --git a/backend/database/migrations/2026_06_01_062350_create_password_reset_otps_table.php b/backend/database/migrations/2026_06_01_062350_create_password_reset_otps_table.php new file mode 100755 index 00000000..2b229b8e --- /dev/null +++ b/backend/database/migrations/2026_06_01_062350_create_password_reset_otps_table.php @@ -0,0 +1,30 @@ +id(); + $table->string('email')->index(); + $table->string('otp'); + $table->string('guard_name'); + $table->timestamp('expires_at'); + $table->timestamp('verified_at')->nullable(); + $table->unsignedTinyInteger('attempts')->default(0); + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::dropIfExists('password_reset_otps'); + } +}; -- 2.49.1