Compare commits

...

26 Commits

Author SHA1 Message Date
1a2077a164 create expert login resources 2026-05-13 13:41:12 +03:30
363c7005d0 Merge pull request 'add level to users table' (#11) from feature/docConfirm into develop
Reviewed-on: #11
2026-05-12 11:03:44 +00:00
f29510d76e add level to users table 2026-05-12 14:32:18 +03:30
fda129875e Merge pull request 'create new route for expert' (#10) from feature/docConfirm into develop
Reviewed-on: #10
2026-05-12 10:23:05 +00:00
ca71547657 create new route for expert 2026-05-12 13:50:48 +03:30
2d31eccc4a Merge pull request 'create user documents' (#9) from feature/docConfirm into develop
Reviewed-on: #9
2026-05-12 09:35:37 +00:00
abb92b4e22 create user documents 2026-05-12 13:04:15 +03:30
ea68943354 Merge pull request 'create city and province resources' (#8) from feature/docConfirm into develop
Reviewed-on: #8
2026-05-12 07:39:30 +00:00
e3831e98c9 create city and province resources 2026-05-12 11:08:47 +03:30
9794aa1f54 Merge pull request 'create EmailEvent for login' (#7) from feature/UploadData into develop
Reviewed-on: #7
2026-05-10 11:01:41 +00:00
4afcd5c81e create EmailEvent for login 2026-05-10 14:29:52 +03:30
74bc255d82 Merge pull request 'make auth service global' (#6) from feature/expertPage into develop
Reviewed-on: #6
2026-05-10 10:56:03 +00:00
ca094cff9f prevent request forgery 2026-05-10 14:25:12 +03:30
c2328ec666 make auth service global 2026-05-09 09:09:49 +03:30
e4f7b96b92 Merge pull request 'create loggin and register whit their form requst' (#4) from feature/AuthPasport into develop
Reviewed-on: #4
2026-05-06 05:51:11 +00:00
f072bd1ee8 delete lan 2026-05-06 09:16:35 +03:30
f01992368e create user info 2026-05-06 09:12:29 +03:30
161fa3ae1e Merge pull request 'add languages files' (#5) from feature/MultiLang into develop
Reviewed-on: #5
2026-05-06 05:21:31 +00:00
b32ee444ca write for user 2026-05-05 17:33:34 +03:30
cee480fff5 improve the code in some part of the project 2026-05-03 15:29:57 +03:30
5024a242d2 fix code 2026-05-03 15:28:50 +03:30
0bba710b7d fix code 2026-05-03 15:11:29 +03:30
f788f9a7ea improve code and fix their bugs 2026-05-02 17:07:40 +03:30
e74619d0ab reviwe my form request in login 2026-05-02 15:29:09 +03:30
5b42e23afc improve form requst 2026-05-02 09:09:34 +03:30
51aa3b157c create loggin and register whit their form requst 2026-04-29 16:52:18 +03:30
52 changed files with 1249 additions and 31 deletions

3
.gitignore vendored
View File

@@ -1,2 +1,3 @@
.idea
.env
.env
front

View File

@@ -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

View File

@@ -0,0 +1,62 @@
<?php
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(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];
if (filter_var($request->login, FILTER_VALIDATE_EMAIL)) {
$credentials['email'] = $request->login;
} else {
$credentials['username'] = $request->login;
}
if (Auth::attempt($credentials))
{
$request->session()->regenerate();
return $this->successResponse();
}
return $this->errorResponse(__('messages.incorrect_or_username_password'));
}
public function logout(Request $request): JsonResponse
{
Auth::guard('expert')->logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return $this->successResponse();
}
}

View File

@@ -0,0 +1,55 @@
<?php
namespace App\Admin\Controllers;
use App\Admin\Requests\Documentation\RejectRequest;
use App\Facades\DataTable\DataTableFacade;
use App\Http\Controllers\Controller;
use App\Models\User;
use App\Traits\ApiResponse;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class DocumentationController extends Controller
{
use ApiResponse;
public function index(Request $request): array
{
return DataTableFacade::run(
User::query(),
$request,
allowedFilters: ['*'],
allowedSortings: ['*'],
allowedSelects: [
'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([
'authority_level' => 1
]);
return $this->successResponse();
}
public function reject(RejectRequest $request, User $user): JsonResponse
{
$user->update([
'kyc_status' => 0,
'kyc_reject_reason' => $request->kyc_reject_reason
]);
return $this->successResponse();
}
}

View File

@@ -0,0 +1,37 @@
<?php
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
{
use ApiResponse;
public function info(): JsonResponse
{
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();
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace App\Admin\Requests\Auth;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Foundation\Http\FormRequest;
class LoginRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, ValidationRule|array|string>
*/
public function rules(): array
{
return [
'login' => 'required|string',
'password' => 'required|string',
];
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace App\Admin\Requests\Auth;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Foundation\Http\FormRequest;
class RegisterRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, ValidationRule|array|string>
*/
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',
];
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace App\Admin\Requests\Documentation;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Foundation\Http\FormRequest;
class RejectRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, ValidationRule|array|string>
*/
public function rules(): array
{
return [
'kyc_reject_reason' => 'required|string',
];
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace App\Admin\Requests\Profile;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Foundation\Http\FormRequest;
class ChangePasswordRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, ValidationRule|array|string>
*/
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',
];
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace App\Admin\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class ExpertResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'username' => $this->username,
'email' => $this->email,
];
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace App\Mail;
use App\Models\User;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
class LoginNotificationMail extends Mailable
{
use Queueable, SerializesModels;
public function __construct(
public User $user
) {
//
}
public function envelope(): Envelope
{
return new Envelope(
subject: 'Login Notification Mail',
);
}
public function content(): Content
{
return new Content(
view: 'emails.login-notification',
);
}
public function attachments(): array
{
return [];
}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace App\Models;
use Database\Factories\CityFactory;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class City extends Model
{
/** @use HasFactory<CityFactory> */
use HasFactory;
protected $guarded = ['id'];
}

View File

@@ -0,0 +1,30 @@
<?php
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<DocumentFactory> */
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)
);
}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace App\Models;
use Database\Factories\ExpertFactory;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Expert extends Model
{
/** @use HasFactory<ExpertFactory> */
use HasFactory;
protected $guarded = ['id'];
}

View File

@@ -0,0 +1,15 @@
<?php
namespace App\Models;
use Database\Factories\ProvinceFactory;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Province extends Model
{
/** @use HasFactory<ProvinceFactory> */
use HasFactory;
protected $guarded = ['id'];
}

19
backend/app/Models/User.php Normal file → Executable file
View File

@@ -5,29 +5,24 @@ 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\Database\Eloquent\Relations\MorphToMany;
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<UserFactory> */
use HasFactory, Notifiable, HasApiTokens;
protected $guarded = ['id'];
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
public function documents(): MorphToMany
{
return [
'email_verified_at' => 'datetime',
'password' => 'hashed',
];
return $this->morphToMany(Document::class, 'documentable');
}
}

2
backend/app/Providers/AppServiceProvider.php Normal file → Executable file
View File

@@ -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::minutes(1));
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace App\Traits;
use Illuminate\Http\JsonResponse;
trait ApiResponse
{
public function successResponse(mixed $data = null, string $message = null, int $statusCode = 200): JsonResponse
{
if (!is_null($data)) {
return response()->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);
}
}

View File

@@ -0,0 +1,64 @@
<?php
namespace App\User\Controllers;
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 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(RegisterRequest $request): JsonResponse
{
$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();
}
public function login(LoginRequest $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();
return $this->successResponse();
}
return $this->errorResponse(__('messages.incorrect_or_username_password'));
}
public function logout(Request $request): JsonResponse
{
Auth::guard('web')->logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return $this->successResponse();
}
}

View File

@@ -0,0 +1,14 @@
<?php
namespace App\User\Controllers\Dashboard;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class ConfirmController extends Controller
{
public function index()
{
//
}
}

View File

@@ -0,0 +1,56 @@
<?php
namespace App\User\Controllers;
use App\Http\Controllers\Controller;
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;
class ProfileController extends Controller
{
use ApiResponse;
public function info(): JsonResponse
{
return $this->successResponse(new UserResource(Auth::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,
'phone_number' => $request->phone_number,
'province_id' => $request->province_id,
'city_id' => $request->city_id,
'status' => 1
]);
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();
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace App\User\Events;
use App\Models\User;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class LoginEmailEvent
{
use Dispatchable, InteractsWithSockets, SerializesModels;
/**
* Create a new event instance.
*/
public function __construct(public User $user)
{
//
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace App\User\Listeners;
use App\Mail\LoginNotificationMail;
use App\User\Events\LoginEmailEvent;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Mail;
class SendEmailListener implements ShouldQueue{
public string $queue = 'emails';
/**
* Create the event listener.
*/
public function __construct()
{
//
}
/**
* Handle the event.
*/
public function handle(LoginEmailEvent $event): void
{
$user = $event->user;
Mail::to($user->email)->send(
new LoginNotificationMail($user)
);
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace App\User\Requests\Auth;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Foundation\Http\FormRequest;
class LoginRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, ValidationRule|array|string>
*/
public function rules(): array
{
return [
'login' => 'required|string',
'password' => 'required|string',
];
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace App\User\Requests\Auth;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Foundation\Http\FormRequest;
class RegisterRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, ValidationRule|array|string>
*/
public function rules(): array
{
return [
'username' => 'required|unique:users,username',
'email' => 'required|email|unique:users,email',
'password' => 'required|string|regex:/^(?=.*[a-zA-Z])(?=.*\d).+$/|min:6|confirmed',
];
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace App\User\Requests\Profile;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Foundation\Http\FormRequest;
class ChangePasswordRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, ValidationRule|array|string>
*/
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',
];
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace App\User\Requests\Profile;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Foundation\Http\FormRequest;
class EditRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, ValidationRule|array|string>
*/
public function rules(): array
{
return [
'first_name' => 'required|string',
'last_name' => 'required|string',
'national_id' => 'required',
'address' => 'required',
'postal_code' => 'required',
'phone_number' => 'required',
'province_id' => 'required',
'city_id' => 'required',
];
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace App\User\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class UserResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'username' => $this->username,
'email' => $this->email,
];
}
}

13
backend/bootstrap/app.php Normal file → Executable file
View File

@@ -10,10 +10,19 @@ return Application::configure(basePath: dirname(__DIR__))
api: __DIR__.'/../routes/api.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
then: function () {
Route::middleware('web')
->prefix('expert')
->name('expert.')
->group(base_path('routes/expert.php'));
},
)
->withMiddleware(function (Middleware $middleware): void {
//
$middleware->web()->preventRequestForgery(['*']);
})
->withExceptions(function (Exceptions $exceptions): void {
//
})->create();
})
->withEvents(discover: [
__DIR__.'/../app/User/Listeners',
])->create();

8
backend/config/auth.php Normal file → Executable file
View File

@@ -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',

34
backend/config/cors.php Executable file
View File

@@ -0,0 +1,34 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Cross-Origin Resource Sharing (CORS) Configuration
|--------------------------------------------------------------------------
|
| Here you may configure your settings for cross-origin resource sharing
| or "CORS". This determines what cross-origin operations may execute
| in web browsers. You are free to adjust these settings as needed.
|
| To learn more: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
|
*/
'paths' => ['*'],
'allowed_methods' => ['*'],
'allowed_origins' => ['*'],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => true,
];

0
backend/config/session.php Normal file → Executable file
View File

View File

@@ -0,0 +1,24 @@
<?php
namespace Database\Factories;
use App\Models\City;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends Factory<City>
*/
class CityFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
//
];
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Database\Factories;
use App\Models\Document;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends Factory<Document>
*/
class DocumentFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
//
];
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Database\Factories;
use App\Models\Expert;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends Factory<Expert>
*/
class ExpertFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
//
];
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Database\Factories;
use App\Models\Province;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends Factory<Province>
*/
class ProvinceFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
//
];
}
}

2
backend/database/factories/UserFactory.php Normal file → Executable file
View File

@@ -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'),

View File

@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('provinces', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('provinces');
}
};

View File

@@ -0,0 +1,29 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('cities', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->foreignId('province_id')->constrained('provinces');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('cities');
}
};

View File

@@ -13,20 +13,26 @@ 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->tinyInteger('kyc_status')->default(0);
$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');
$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();
});
Schema::create('password_reset_tokens', function (Blueprint $table) {
$table->string('email')->primary();
$table->string('token');
$table->timestamp('created_at')->nullable();
});
Schema::create('sessions', function (Blueprint $table) {
$table->string('id')->primary();
$table->foreignId('user_id')->nullable()->index();
@@ -43,7 +49,6 @@ return new class extends Migration
public function down(): void
{
Schema::dropIfExists('users');
Schema::dropIfExists('password_reset_tokens');
Schema::dropIfExists('sessions');
}
};

View File

@@ -0,0 +1,30 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('documents', function (Blueprint $table) {
$table->id();
$table->morphs('documentable');
$table->string('title');
$table->string('url');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('documents');
}
};

View File

@@ -0,0 +1,38 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('experts', function (Blueprint $table) {
$table->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->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('experts');
}
};

View File

@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->string('kyc_reject_reason')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('kyc_reject_reason');
});
}
};

View File

@@ -0,0 +1,17 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class CitySeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
//
}
}

3
backend/database/seeders/DatabaseSeeder.php Normal file → Executable file
View File

@@ -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@',
]);
}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class DocumentSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
//
}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class ExpertSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
//
}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class ProvinceSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
//
}
}

View File

@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Login Notification</title>
</head>
<body>
<h2>Hello {{ $user->username }}</h2>
<p>You have successfully logged in to your account.</p>
<p>
Email: {{ $user->email }}
</p>
<p>
If this login was not done by you, please contact support immediately.
</p>
<br>
<p>Thank you.</p>
</body>
</html>

0
backend/routes/api.php Normal file → Executable file
View File

24
backend/routes/expert.php Executable file
View File

@@ -0,0 +1,24 @@
<?php
use App\Admin\Controllers\AuthController;
use App\Admin\Controllers\DocumentationController;
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')->name('logout');
});
Route::prefix('documents')
->name('documents.')
->middleware('auth:expert')
->controller(DocumentationController::class)
->group(function () {
Route::get('/', 'index')->name('index');
Route::post('confirm', 'confirm')->name('confirm');
Route::post('reject', 'reject')->name('reject');
Route::get('files/{user}', 'files')->name('files');
});

23
backend/routes/web.php Normal file → Executable file
View File

@@ -1,7 +1,24 @@
<?php
use App\User\Controllers\AuthController;
use App\User\Controllers\ProfileController;
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
return view('welcome');
});
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')->name('logout');
});
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');
});