diff --git a/.env.example b/.env.example index ca01994..ba4f73e 100644 --- a/.env.example +++ b/.env.example @@ -63,3 +63,7 @@ AWS_BUCKET= AWS_USE_PATH_STYLE_ENDPOINT=false VITE_APP_NAME="${APP_NAME}" + +# External API Configuration +API_URL=http://localhost:8007/api/v1 +API_TIMEOUT=30 diff --git a/API_PROXY_SETUP.md b/API_PROXY_SETUP.md index 1e3a052..3830f7b 100644 --- a/API_PROXY_SETUP.md +++ b/API_PROXY_SETUP.md @@ -50,16 +50,34 @@ Special endpoint for handling file uploads with multipart form data. ## Frontend Usage -The `api-client.js` is automatically configured to use the proxy: +The `api-client.js` is automatically configured to use the proxy for all API requests: ```javascript -// Before: Direct external API calls -this.baseUrl = 'http://telegram.localhost:8009/api/v1'; - -// After: Local proxy calls +// All requests go through the local proxy this.baseUrl = '/api/proxy'; + +// The external API URL is read from meta tag set by PHP +// This is used for direct health checks and testing connections +this.externalApiUrl = this.getApiUrlFromMeta(); +window.API_URL = this.externalApiUrl; +``` + +### Configuration in .env + +```env +# External API URL (used by Laravel and exposed to frontend via meta tag) +API_URL=http://localhost:8007/api/v1 +API_TIMEOUT=30 ``` +The API URL is injected into the HTML head as a meta tag by Laravel: + +```html + +``` + +This ensures the API endpoint is configured in one place (`.env`) and properly propagated to both backend (Laravel) and frontend (JavaScript). + ## Security Features - **Authentication**: All proxy routes require `auth` and `verified` middleware diff --git a/FRONTEND_ARCHITECTURE.md b/FRONTEND_ARCHITECTURE.md index 131a969..a68d91c 100644 --- a/FRONTEND_ARCHITECTURE.md +++ b/FRONTEND_ARCHITECTURE.md @@ -70,31 +70,34 @@ const shifts = await window.apiClient.getCurrentShifts(); ### 2. Configuration -Set the API base URL by defining `window.API_BASE_URL` before loading the API client: +The API URL is configured via environment variable in `.env`: -```html - - +```env +# External API URL (used by Laravel and exposed to frontend) +API_URL=http://localhost:8007/api/v1 +API_TIMEOUT=30 ``` -Or configure it in your `.env`: +The API URL is passed from Laravel to JavaScript via a meta tag in the HTML head: -```env -VITE_API_BASE_URL=https://your-api-server.com/api/v1 +```html + ``` -Then in your `vite.config.js`: +The API client reads this meta tag on initialization: ```javascript -export default defineConfig({ - define: { - 'window.API_BASE_URL': JSON.stringify(env.VITE_API_BASE_URL || 'http://localhost:8000/api/v1') +getApiUrlFromMeta() { + const metaTag = document.querySelector('meta[name="api-url"]'); + if (metaTag) { + return metaTag.getAttribute('content'); } -}); + return 'http://localhost:8007/api/v1'; // Fallback +} ``` +The API client automatically exposes this URL as `window.API_URL` for use in views and inline scripts. + ### 3. View Implementation Pattern All views use Alpine.js to fetch and display data dynamically. Example pattern: @@ -224,7 +227,7 @@ npm run dev Set the Telegram Bot API URL in your environment: ```env -VITE_API_BASE_URL=https://your-telegram-bot-api.com/api/v1 +API_URL=https://your-telegram-bot-api.com/api/v1 ``` ### 3. Test API Connectivity diff --git a/app/Http/Controllers/ApiProxyController.php b/app/Http/Controllers/ApiProxyController.php index 49fc055..47f770f 100644 --- a/app/Http/Controllers/ApiProxyController.php +++ b/app/Http/Controllers/ApiProxyController.php @@ -5,7 +5,6 @@ use Illuminate\Http\Request; use Illuminate\Http\JsonResponse; use Illuminate\Support\Facades\Http; -use App\Models\Setting; class ApiProxyController extends Controller { @@ -15,20 +14,24 @@ class ApiProxyController extends Controller public function proxy(Request $request, string $endpoint): JsonResponse { try { - // Get user's API settings - $apiUrl = Setting::getValue('api_url', 'http://host.docker.internal:8007/api/v1'); - $apiToken = Setting::getValue('auth_token'); + // Get API URL from configuration + $apiUrl = config('api.url'); - if (!$apiToken) { + // Get token from session + $apiToken = $request->session()->get('api_token'); + + // Define endpoints that don't require authentication + $publicEndpoints = ['register', 'session']; + $isPublicEndpoint = in_array(trim($endpoint, '/'), $publicEndpoints); + + // Check authentication for protected endpoints + if (!$isPublicEndpoint && !$apiToken) { return response()->json([ - 'error' => 'API token not configured', - 'message' => 'Please configure your Telegram Bot API token in settings' + 'error' => 'Authentication required', + 'message' => 'Please login to access this resource' ], 401); } - // Replace localhost with host.docker.internal for Docker environment - $apiUrl = str_replace('http://localhost:', 'http://host.docker.internal:', $apiUrl); - // Build the external URL $externalUrl = rtrim($apiUrl, '/') . '/' . ltrim($endpoint, '/'); @@ -36,9 +39,13 @@ public function proxy(Request $request, string $endpoint): JsonResponse $headers = [ 'Accept' => 'application/json', 'Content-Type' => 'application/json', - 'Authorization' => "Bearer {$apiToken}", ]; + // Add authorization header only if we have a token + if ($apiToken) { + $headers['Authorization'] = "Bearer {$apiToken}"; + } + // Add any custom headers from the original request if ($request->header('X-Requested-With')) { $headers['X-Requested-With'] = $request->header('X-Requested-With'); @@ -46,7 +53,7 @@ public function proxy(Request $request, string $endpoint): JsonResponse // Make the external request $response = Http::withHeaders($headers) - ->timeout(30) + ->timeout(config('api.timeout')) ->send( $request->method(), $externalUrl, @@ -67,7 +74,6 @@ public function proxy(Request $request, string $endpoint): JsonResponse \Log::error('API Proxy Error: ' . $e->getMessage(), [ 'endpoint' => $endpoint, 'method' => $request->method(), - 'user_id' => auth()->id(), ]); return response()->json([ @@ -84,19 +90,18 @@ public function proxy(Request $request, string $endpoint): JsonResponse public function proxyUpload(Request $request, string $endpoint): JsonResponse { try { - // Get user's API settings - $apiUrl = Setting::getValue('api_url', 'http://host.docker.internal:8007/api/v1'); - $apiToken = Setting::getValue('auth_token'); + // Get API URL from configuration + $apiUrl = config('api.url'); + + // Get token from session + $apiToken = $request->session()->get('api_token'); if (!$apiToken) { return response()->json([ - 'error' => 'API token not configured' + 'error' => 'Authentication required' ], 401); } - // Replace localhost with host.docker.internal for Docker environment - $apiUrl = str_replace('http://localhost:', 'http://host.docker.internal:', $apiUrl); - $externalUrl = rtrim($apiUrl, '/') . '/' . ltrim($endpoint, '/'); // Prepare headers @@ -142,7 +147,6 @@ public function proxyUpload(Request $request, string $endpoint): JsonResponse } catch (\Exception $e) { \Log::error('API Proxy Upload Error: ' . $e->getMessage(), [ 'endpoint' => $endpoint, - 'user_id' => auth()->id(), ]); return response()->json([ diff --git a/app/Http/Controllers/Auth/ConfirmationController.php b/app/Http/Controllers/Auth/ConfirmationController.php deleted file mode 100644 index 0a54e65..0000000 --- a/app/Http/Controllers/Auth/ConfirmationController.php +++ /dev/null @@ -1,34 +0,0 @@ -validate([ - 'email' => $request->user()->email, - 'password' => $request->password, - ])) { - throw ValidationException::withMessages([ - 'password' => __('auth.password'), - ]); - } - - $request->session()->put('auth.password_confirmed_at', time()); - - return redirect()->intended(route('dashboard', absolute: false)); - } -} diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index 46ab9e5..58d8d90 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -6,7 +6,7 @@ use Illuminate\Auth\Events\Lockout; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; -use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\RateLimiter; use Illuminate\Support\Str; use Illuminate\Validation\ValidationException; @@ -19,36 +19,63 @@ public function create(): View return view('auth.login'); } - public function store(Request $request): RedirectResponse + public function store(Request $request): RedirectResponse|\Illuminate\Http\JsonResponse { + // Frontend has already called the API and is just storing token/user in session $request->validate([ - 'email' => ['required', 'string', 'email'], - 'password' => ['required', 'string'], + 'token' => ['required', 'string'], + 'user' => ['required', 'array'], ]); - $this->ensureIsNotRateLimited($request); - - if (! Auth::attempt($request->only('email', 'password'), $request->boolean('remember'))) { - RateLimiter::hit($this->throttleKey($request)); + try { + // Store authentication data in session + $request->session()->regenerate(); + $request->session()->put('api_token', $request->input('token')); + $request->session()->put('user', $request->input('user')); + $request->session()->put('authenticated', true); + + if ($request->expectsJson()) { + return response()->json([ + 'success' => true, + 'message' => 'Session created successfully', + 'redirect' => route('dashboard', absolute: false) + ]); + } + + return redirect()->intended(route('dashboard', absolute: false)); + + } catch (\Exception $e) { + if ($request->expectsJson()) { + return response()->json([ + 'message' => 'Session creation failed', + 'errors' => ['token' => ['Session creation failed']] + ], 500); + } throw ValidationException::withMessages([ - 'email' => trans('auth.failed'), + 'token' => 'Session creation failed', ]); } - - RateLimiter::clear($this->throttleKey($request)); - - $request->session()->regenerate(); - - return redirect()->intended(route('dashboard', absolute: false)); } public function destroy(Request $request): RedirectResponse { - Auth::guard('web')->logout(); + try { + // Call API logout if we have a token + $token = $request->session()->get('api_token'); + if ($token) { + $apiUrl = config('api.url'); + Http::timeout(config('api.timeout')) + ->withToken($token) + ->delete("{$apiUrl}/session"); + } + } catch (\Exception $e) { + // Log error but continue with local logout + \Log::warning('API logout failed: ' . $e->getMessage()); + } + // Clear session $request->session()->invalidate(); - $request->session()->regenerateToken(); return redirect('/'); diff --git a/app/Http/Controllers/Auth/NewPasswordController.php b/app/Http/Controllers/Auth/NewPasswordController.php deleted file mode 100644 index 5178ab0..0000000 --- a/app/Http/Controllers/Auth/NewPasswordController.php +++ /dev/null @@ -1,54 +0,0 @@ - $request]); - } - - public function store(Request $request): RedirectResponse - { - $request->validate([ - 'token' => ['required'], - 'email' => ['required', 'email'], - 'password' => ['required', 'confirmed', Rules\Password::defaults()], - ]); - - // Here we will attempt to reset the user's password. If it is successful we - // will update the password on an actual user model and persist it to the - // database. Otherwise we will parse the error and return the response. - $status = Password::reset( - $request->only('email', 'password', 'password_confirmation', 'token'), - function (User $user) use ($request) { - $user->forceFill([ - 'password' => Hash::make($request->password), - 'remember_token' => Str::random(60), - ])->save(); - - event(new PasswordReset($user)); - } - ); - - // If the password was successfully reset, we will redirect the user back to - // the application's home authenticated view. If there is an error we can - // redirect them back to where they came from with their error message. - return $status == Password::PASSWORD_RESET - ? to_route('login')->with('status', __($status)) - : back()->withInput($request->only('email')) - ->withErrors(['email' => __($status)]); - } -} diff --git a/app/Http/Controllers/Auth/PasswordResetLinkController.php b/app/Http/Controllers/Auth/PasswordResetLinkController.php deleted file mode 100644 index 6ea25e2..0000000 --- a/app/Http/Controllers/Auth/PasswordResetLinkController.php +++ /dev/null @@ -1,28 +0,0 @@ -validate([ - 'email' => ['required', 'email'], - ]); - - Password::sendResetLink($request->only('email')); - - return back()->with('status', __('A reset link will be sent if the account exists.')); - } -} diff --git a/app/Http/Controllers/Auth/RegistrationController.php b/app/Http/Controllers/Auth/RegistrationController.php index 4c33bec..5806e3f 100644 --- a/app/Http/Controllers/Auth/RegistrationController.php +++ b/app/Http/Controllers/Auth/RegistrationController.php @@ -3,13 +3,10 @@ namespace App\Http\Controllers\Auth; use App\Http\Controllers\Controller; -use App\Models\User; -use Illuminate\Auth\Events\Registered; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; -use Illuminate\Support\Facades\Auth; -use Illuminate\Support\Facades\Hash; -use Illuminate\Validation\Rules; +use Illuminate\Support\Facades\Http; +use Illuminate\Validation\ValidationException; use Illuminate\View\View; class RegistrationController extends Controller @@ -19,20 +16,42 @@ public function create(): View return view('auth.register'); } - public function store(Request $request): RedirectResponse + public function store(Request $request): RedirectResponse|\Illuminate\Http\JsonResponse { - $validated = $request->validate([ - 'name' => ['required', 'string', 'max:255'], - 'email' => ['required', 'string', 'lowercase', 'email', 'max:255', 'unique:'.User::class], - 'password' => ['required', 'confirmed', Rules\Password::defaults()], + // Frontend has already called the API and is just storing token/user in session + $request->validate([ + 'token' => ['required', 'string'], + 'user' => ['required', 'array'], ]); - $validated['password'] = Hash::make($validated['password']); - - event(new Registered(($user = User::create($validated)))); - - Auth::login($user); - - return redirect(route('dashboard', absolute: false)); + try { + // Store authentication data in session + $request->session()->regenerate(); + $request->session()->put('api_token', $request->input('token')); + $request->session()->put('user', $request->input('user')); + $request->session()->put('authenticated', true); + + if ($request->expectsJson()) { + return response()->json([ + 'success' => true, + 'message' => 'Session created successfully', + 'redirect' => route('dashboard', absolute: false) + ]); + } + + return redirect(route('dashboard', absolute: false)); + + } catch (\Exception $e) { + if ($request->expectsJson()) { + return response()->json([ + 'message' => 'Session creation failed', + 'errors' => ['token' => ['Session creation failed']] + ], 500); + } + + throw ValidationException::withMessages([ + 'token' => 'Session creation failed', + ]); + } } } diff --git a/app/Http/Controllers/Auth/VerificationController.php b/app/Http/Controllers/Auth/VerificationController.php deleted file mode 100644 index ed60bf4..0000000 --- a/app/Http/Controllers/Auth/VerificationController.php +++ /dev/null @@ -1,47 +0,0 @@ -user()->hasVerifiedEmail() - ? redirect()->intended(route('dashboard', absolute: false)) - : view('auth.verify-email'); - } - - public function store(Request $request): RedirectResponse - { - if ($request->user()->hasVerifiedEmail()) { - return redirect()->intended(route('dashboard', absolute: false)); - } - - $request->user()->sendEmailVerificationNotification(); - - return back()->with('status', 'verification-link-sent'); - } - - public function verify(EmailVerificationRequest $request): RedirectResponse - { - if ($request->user()->hasVerifiedEmail()) { - return redirect()->intended(route('dashboard', absolute: false).'?verified=1'); - } - - if ($request->user()->markEmailAsVerified()) { - /** @var \Illuminate\Contracts\Auth\MustVerifyEmail $user */ - $user = $request->user(); - - event(new Verified($user)); - } - - return redirect()->intended(route('dashboard', absolute: false).'?verified=1'); - } -} diff --git a/app/Http/Controllers/Settings/AppearanceController.php b/app/Http/Controllers/Settings/AppearanceController.php deleted file mode 100644 index f8caff7..0000000 --- a/app/Http/Controllers/Settings/AppearanceController.php +++ /dev/null @@ -1,14 +0,0 @@ -get(); + // Use session ID as user_id for settings + $sessionId = $request->session()->getId(); + $settings = Setting::where('user_id', $sessionId)->get(); return response()->json([ 'data' => $settings @@ -34,9 +35,11 @@ public function index(): JsonResponse /** * Get specific setting by key. */ - public function show(string $key): JsonResponse + public function show(Request $request, string $key): JsonResponse { - $setting = Setting::where('user_id', Auth::id()) + // Use session ID as user_id for settings + $sessionId = $request->session()->getId(); + $setting = Setting::where('user_id', $sessionId) ->where('key', $key) ->first(); @@ -61,10 +64,13 @@ public function update(Request $request, string $key): JsonResponse return response()->json(['errors' => $validator->errors()], 422); } + // Use session ID as user_id for settings + $sessionId = $request->session()->getId(); $setting = Setting::setValue( $key, $request->input('value'), - $request->input('type', 'string') + $request->input('type', 'string'), + $sessionId ); return response()->json(['data' => $setting]); @@ -94,7 +100,9 @@ public function bulkUpdate(Request $request): JsonResponse ]; } - $results = Setting::setBulk($settingsData); + // Use session ID as user_id for settings + $sessionId = $request->session()->getId(); + $results = Setting::setBulk($settingsData, $sessionId); return response()->json(['data' => array_values($results)]); } @@ -102,9 +110,11 @@ public function bulkUpdate(Request $request): JsonResponse /** * Delete a setting. */ - public function destroy(string $key): JsonResponse + public function destroy(Request $request, string $key): JsonResponse { - $setting = Setting::where('user_id', Auth::id()) + // Use session ID as user_id for settings + $sessionId = $request->session()->getId(); + $setting = Setting::where('user_id', $sessionId) ->where('key', $key) ->first(); diff --git a/app/Http/Controllers/Settings/PasswordController.php b/app/Http/Controllers/Settings/PasswordController.php deleted file mode 100644 index 60d3685..0000000 --- a/app/Http/Controllers/Settings/PasswordController.php +++ /dev/null @@ -1,34 +0,0 @@ - $request->user(), - ]); - } - - public function update(Request $request): RedirectResponse - { - $validated = $request->validate([ - 'current_password' => ['required', 'current_password'], - 'password' => ['required', Rules\Password::defaults(), 'confirmed'], - ]); - - $request->user()->update([ - 'password' => Hash::make($validated['password']), - ]); - - return back()->with('status', 'password-updated'); - } -} diff --git a/app/Http/Controllers/Settings/ProfileController.php b/app/Http/Controllers/Settings/ProfileController.php deleted file mode 100644 index a0c1bc9..0000000 --- a/app/Http/Controllers/Settings/ProfileController.php +++ /dev/null @@ -1,62 +0,0 @@ - $request->user(), - ]); - } - - public function update(Request $request): RedirectResponse - { - $user = $request->user(); - - $validated = $request->validate([ - 'name' => ['required', 'string', 'max:255'], - 'email' => [ - 'required', - 'string', - 'lowercase', - 'email', - 'max:255', - Rule::unique(User::class)->ignore($user->id), - ], - ]); - - $user->fill($validated); - - if ($user->isDirty('email')) { - $user->email_verified_at = null; - } - - $user->save(); - - return to_route('settings.profile.edit')->with('status', __('Profile updated successfully')); - } - - public function destroy(Request $request): RedirectResponse - { - $user = $request->user(); - - Auth::logout(); - - $user->delete(); - - $request->session()->invalidate(); - $request->session()->regenerateToken(); - - return to_route('home'); - } -} diff --git a/app/Http/Middleware/ApiAuthenticate.php b/app/Http/Middleware/ApiAuthenticate.php new file mode 100644 index 0000000..eaaad9e --- /dev/null +++ b/app/Http/Middleware/ApiAuthenticate.php @@ -0,0 +1,31 @@ +session()->get('authenticated')) { + return redirect()->guest(route('login')); + } + + // Check if we have an API token + if (!$request->session()->get('api_token')) { + $request->session()->flush(); + return redirect()->guest(route('login')); + } + + return $next($request); + } +} diff --git a/app/Http/Middleware/ApiGuest.php b/app/Http/Middleware/ApiGuest.php new file mode 100644 index 0000000..0cda2d5 --- /dev/null +++ b/app/Http/Middleware/ApiGuest.php @@ -0,0 +1,25 @@ +session()->get('authenticated')) { + return redirect(route('dashboard')); + } + + return $next($request); + } +} diff --git a/app/Models/Setting.php b/app/Models/Setting.php index 711952b..ed1ac01 100644 --- a/app/Models/Setting.php +++ b/app/Models/Setting.php @@ -27,9 +27,9 @@ public function user() return $this->belongsTo(User::class); } - public static function getValue(string $key, mixed $default = null, ?int $userId = null) + public static function getValue(string $key, mixed $default = null, ?string $userId = null) { - $userId = $userId ?? auth()->id(); + $userId = $userId ?? session()->getId(); $setting = static::where('user_id', $userId) ->where('key', $key) @@ -48,9 +48,9 @@ public static function getValue(string $key, mixed $default = null, ?int $userId }; } - public static function setValue(string $key, mixed $value, string $type = 'string', ?int $userId = null) + public static function setValue(string $key, mixed $value, string $type = 'string', ?string $userId = null) { - $userId = $userId ?? auth()->id(); + $userId = $userId ?? session()->getId(); $processedValue = match ($type) { 'boolean' => $value ? 'true' : 'false', @@ -64,9 +64,9 @@ public static function setValue(string $key, mixed $value, string $type = 'strin ); } - public static function getBulk(array $keys, ?int $userId = null) + public static function getBulk(array $keys, ?string $userId = null) { - $userId = $userId ?? auth()->id(); + $userId = $userId ?? session()->getId(); return static::where('user_id', $userId) ->whereIn('key', $keys) @@ -77,9 +77,9 @@ public static function getBulk(array $keys, ?int $userId = null) ->toArray(); } - public static function setBulk(array $settings, ?int $userId = null) + public static function setBulk(array $settings, ?string $userId = null) { - $userId = $userId ?? auth()->id(); + $userId = $userId ?? session()->getId(); $results = []; foreach ($settings as $key => $data) { diff --git a/bootstrap/app.php b/bootstrap/app.php index 7b162da..cdc1feb 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -11,7 +11,16 @@ health: '/up', ) ->withMiddleware(function (Middleware $middleware) { - // + $middleware->alias([ + 'api.auth' => \App\Http\Middleware\ApiAuthenticate::class, + 'api.guest' => \App\Http\Middleware\ApiGuest::class, + ]); + + // Exclude auth routes from CSRF verification since they communicate directly with external API + $middleware->validateCsrfTokens(except: [ + '/login', + '/register', + ]); }) ->withExceptions(function (Exceptions $exceptions) { // diff --git a/build-output.log b/build-output.log new file mode 100644 index 0000000..7d96a61 --- /dev/null +++ b/build-output.log @@ -0,0 +1,13 @@ + +> build +> vite build + +vite v6.3.6 building for production... +transforming... +✓ 55 modules transformed. +rendering chunks... +computing gzip size... +public/build/manifest.json 0.27 kB │ gzip: 0.15 kB +public/build/assets/app-Dni5MQcH.css 52.28 kB │ gzip: 11.16 kB +public/build/assets/app-Y5v3OA_z.js 86.10 kB │ gzip: 31.78 kB +✓ built in 1.46s diff --git a/build.log b/build.log new file mode 100644 index 0000000..383c068 --- /dev/null +++ b/build.log @@ -0,0 +1,13 @@ + +> build +> vite build + +vite v6.3.6 building for production... +transforming... +✓ 55 modules transformed. +rendering chunks... +computing gzip size... +public/build/manifest.json 0.27 kB │ gzip: 0.15 kB +public/build/assets/app-Dni5MQcH.css 52.28 kB │ gzip: 11.16 kB +public/build/assets/app-Y5v3OA_z.js 86.10 kB │ gzip: 31.78 kB +✓ built in 2.38s diff --git a/config/api.php b/config/api.php new file mode 100644 index 0000000..8b03c00 --- /dev/null +++ b/config/api.php @@ -0,0 +1,16 @@ + env('API_URL', 'http://localhost:8007/api/v1'), + 'timeout' => env('API_TIMEOUT', 30), +]; diff --git a/database/migrations/0001_01_01_000000_create_users_table.php b/database/migrations/0001_01_01_000000_create_users_table.php deleted file mode 100644 index 05fb5d9..0000000 --- a/database/migrations/0001_01_01_000000_create_users_table.php +++ /dev/null @@ -1,49 +0,0 @@ -id(); - $table->string('name'); - $table->string('email')->unique(); - $table->timestamp('email_verified_at')->nullable(); - $table->string('password'); - $table->rememberToken(); - $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(); - $table->string('ip_address', 45)->nullable(); - $table->text('user_agent')->nullable(); - $table->longText('payload'); - $table->integer('last_activity')->index(); - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::dropIfExists('users'); - Schema::dropIfExists('password_reset_tokens'); - Schema::dropIfExists('sessions'); - } -}; diff --git a/npm-install.log b/npm-install.log new file mode 100644 index 0000000..b8ee6ef --- /dev/null +++ b/npm-install.log @@ -0,0 +1,12 @@ + +added 248 packages, and audited 249 packages in 8s + +52 packages are looking for funding + run `npm fund` for details + +1 moderate severity vulnerability + +To address all issues, run: + npm audit fix + +Run `npm audit` for details. diff --git a/package-lock.json b/package-lock.json index 560fdff..f4048e1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "TaskMateFrontend", + "name": "gh-issue-solver-1761647480052", "lockfileVersion": 3, "requires": true, "packages": { @@ -3228,7 +3228,6 @@ "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -3276,7 +3275,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", @@ -3413,7 +3411,6 @@ "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -4136,7 +4133,6 @@ "integrity": "sha512-0msEVHJEScQbhkbVTb/4iHZdJ6SXp/AvxL2sjwYQFfBqleHtnCqv1J3sa9zbWz/6kW1m9Tfzn92vW+kZ1WV6QA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", diff --git a/pint.log b/pint.log new file mode 100644 index 0000000..3235941 --- /dev/null +++ b/pint.log @@ -0,0 +1 @@ +/bin/bash: line 1: php: command not found diff --git a/resources/js/api-client.js b/resources/js/api-client.js index 41c7899..ee14393 100644 --- a/resources/js/api-client.js +++ b/resources/js/api-client.js @@ -9,11 +9,33 @@ class ApiClient { constructor() { // API base URL - now uses local proxy endpoint - this.baseUrl = window.API_BASE_URL || '/api/proxy'; + // The proxy forwards requests to the external API configured in .env (API_URL) + this.baseUrl = '/api/proxy'; + + // Store the external API URL for direct access when needed (e.g., health checks) + // This is read from the meta tag set by PHP (Laravel config) + this.externalApiUrl = this.getApiUrlFromMeta(); + + // Make API URL available globally for views + window.API_URL = this.externalApiUrl; + // Token is now handled server-side in the proxy this.token = null; } + /** + * Get API URL from meta tag + */ + getApiUrlFromMeta() { + const metaTag = document.querySelector('meta[name="api-url"]'); + if (metaTag) { + return metaTag.getAttribute('content'); + } + // Fallback for development if meta tag is missing + console.warn('API URL meta tag not found, using fallback'); + return 'http://localhost:8007/api/v1'; + } + /** * Refresh CSRF token */ @@ -202,12 +224,36 @@ class ApiClient { } /** - * Health check + * Health check (via proxy) */ async healthCheck() { return this.get('/up'); } + /** + * Health check for external API (direct connection, bypasses proxy) + * Used for testing API connectivity in settings + */ + async healthCheckDirect() { + try { + const response = await fetch(`${this.externalApiUrl}/up`, { + method: 'GET', + headers: { + 'Accept': 'application/json', + }, + }); + + if (!response.ok) { + throw new Error('API health check failed'); + } + + return await response.json(); + } catch (error) { + console.error('Direct health check error:', error); + throw error; + } + } + // ============================================ // Users Endpoints // ============================================ diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php index df95561..330f01c 100644 --- a/resources/views/auth/login.blade.php +++ b/resources/views/auth/login.blade.php @@ -8,20 +8,17 @@ class="bg-white dark:bg-gray-800 rounded-lg shadow-md border border-gray-200 dar

Sign in to your account

-
- @csrf +
- + +
- @if (Route::has('password.request')) - {{ __('Forgot password?') }} - @endif +
@@ -29,10 +26,135 @@ class="text-xs text-blue-600 dark:text-blue-400 hover:underline">{{ __('Forgot p + + + - {{ __('Sign In') }} + {{ __('Sign In') }} + + @if (Route::has('register'))
diff --git a/resources/views/auth/register.blade.php b/resources/views/auth/register.blade.php index 7939bc9..9bd87a9 100644 --- a/resources/views/auth/register.blade.php +++ b/resources/views/auth/register.blade.php @@ -9,33 +9,161 @@ class="bg-white dark:bg-gray-800 rounded-lg shadow-md border border-gray-200 dar

-
- @csrf - + +
- -
- - -
- + +

+ {{ __('This will be your username for logging in') }} +

+
+

+ {{ __('Minimum 12 characters with uppercase, lowercase, digits, and special characters') }} +

+
+ +
+ + + - {{ __('Create Account') }} + {{ __('Create Account') }}
+ +

diff --git a/resources/views/components/button.blade.php b/resources/views/components/button.blade.php index 7ddb405..40ad549 100644 --- a/resources/views/components/button.blade.php +++ b/resources/views/components/button.blade.php @@ -14,6 +14,6 @@ ]); @endphp -<{{ $tag }} {{ $attributes->merge(['class' => $styleClasses]) }}> +<{{ $tag }} type="{{ $buttonType }}" {{ $attributes->merge(['class' => $styleClasses]) }}> {{ $slot }} diff --git a/resources/views/components/layouts/app.blade.php b/resources/views/components/layouts/app.blade.php index 528bfaf..90e8431 100644 --- a/resources/views/components/layouts/app.blade.php +++ b/resources/views/components/layouts/app.blade.php @@ -6,6 +6,7 @@ {{ config('app.name') }} +