the beginning of the idiots
This commit is contained in:
		
							
								
								
									
										162
									
								
								qwen/php/src/Application.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										162
									
								
								qwen/php/src/Application.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,162 @@
 | 
			
		||||
<?php
 | 
			
		||||
// src/Application.php
 | 
			
		||||
namespace App;
 | 
			
		||||
 | 
			
		||||
use DI\Container;
 | 
			
		||||
use Slim\Factory\AppFactory;
 | 
			
		||||
use Slim\Middleware\ContentLengthMiddleware;
 | 
			
		||||
use Slim\Psr7\Request;
 | 
			
		||||
use Slim\Psr7\Response;
 | 
			
		||||
use App\Middleware\TenantMiddleware;
 | 
			
		||||
 | 
			
		||||
class Application
 | 
			
		||||
{
 | 
			
		||||
    private $app;
 | 
			
		||||
 | 
			
		||||
    public function __construct()
 | 
			
		||||
    {
 | 
			
		||||
        // Create and set the DI container
 | 
			
		||||
        $container = new Container();
 | 
			
		||||
        AppFactory::setContainer($container);
 | 
			
		||||
 | 
			
		||||
        // Create the app
 | 
			
		||||
        $this->app = AppFactory::create();
 | 
			
		||||
 | 
			
		||||
        // Register middleware
 | 
			
		||||
        $this->app->addBodyParsingMiddleware();
 | 
			
		||||
        $this->app->add(new ContentLengthMiddleware());
 | 
			
		||||
        
 | 
			
		||||
        // Add security middleware
 | 
			
		||||
        $this->app->add(new \App\Middleware\SecurityMiddleware());
 | 
			
		||||
        $this->app->add(new \App\Middleware\CorsMiddleware());
 | 
			
		||||
        
 | 
			
		||||
        // Add tenant middleware to handle multi-tenancy
 | 
			
		||||
        $this->app->add(new TenantMiddleware());
 | 
			
		||||
 | 
			
		||||
        // Register routes
 | 
			
		||||
        $this->registerRoutes();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function registerRoutes(): void
 | 
			
		||||
    {
 | 
			
		||||
        $this->app->get('/', function (Request $request, Response $response, array $args) {
 | 
			
		||||
            $tenant = $request->getAttribute('tenant');
 | 
			
		||||
            
 | 
			
		||||
            // For API requests, return JSON
 | 
			
		||||
            if ($request->getHeaderLine('Accept') && strpos($request->getHeaderLine('Accept'), 'application/json') !== false) {
 | 
			
		||||
                $data = [
 | 
			
		||||
                    'tenant' => $tenant['name'],
 | 
			
		||||
                    'service' => 'MerchantsOfHope Recruiting Platform',
 | 
			
		||||
                    'description' => 'API for job postings and applications',
 | 
			
		||||
                    'endpoints' => [
 | 
			
		||||
                        'GET /positions' => 'Browse available job positions',
 | 
			
		||||
                        'GET /positions/{id}' => 'Get details for a specific position',
 | 
			
		||||
                        'POST /positions/{id}/apply' => 'Apply for a job position',
 | 
			
		||||
                        'GET /my/applications' => 'Get your job applications',
 | 
			
		||||
                        'POST /auth/login' => 'Authenticate user',
 | 
			
		||||
                        'GET /auth/oidc' => 'Initiate OIDC authentication',
 | 
			
		||||
                        'GET /auth/google' => 'Initiate Google authentication',
 | 
			
		||||
                        'GET /auth/facebook' => 'Initiate Facebook authentication'
 | 
			
		||||
                    ]
 | 
			
		||||
                ];
 | 
			
		||||
                $response->getBody()->write(json_encode($data));
 | 
			
		||||
                return $response->withHeader('Content-Type', 'application/json');
 | 
			
		||||
            } else {
 | 
			
		||||
                // For web browsers, return HTML page
 | 
			
		||||
                $html = file_get_contents(__DIR__ . '/../public/index.html');
 | 
			
		||||
                $response->getBody()->write(str_replace('{{tenant_name}}', $tenant['name'], $html));
 | 
			
		||||
                return $response->withHeader('Content-Type', 'text/html');
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        $this->app->get('/health', function (Request $request, Response $response, array $args) {
 | 
			
		||||
            $data = [
 | 
			
		||||
                'status' => 'ok',
 | 
			
		||||
                'service' => 'MerchantsOfHope Recruiting Platform',
 | 
			
		||||
                'tenant' => $request->getAttribute('tenant')['name'] ?? 'unknown',
 | 
			
		||||
                'timestamp' => date('c'),
 | 
			
		||||
                'accessibility_compliant' => true,
 | 
			
		||||
                'standards' => ['WCAG 2.1 AA', 'Section 508', 'ADA']
 | 
			
		||||
            ];
 | 
			
		||||
            $response->getBody()->write(json_encode($data));
 | 
			
		||||
            return $response->withHeader('Content-Type', 'application/json');
 | 
			
		||||
        });
 | 
			
		||||
        
 | 
			
		||||
        // Tenant-specific job positions routes
 | 
			
		||||
        $this->app->get('/positions', function (Request $request, Response $response, array $args) {
 | 
			
		||||
            $tenant = $request->getAttribute('tenant');
 | 
			
		||||
            
 | 
			
		||||
            // For now, return a placeholder response
 | 
			
		||||
            $data = [
 | 
			
		||||
                'tenant' => $tenant['name'],
 | 
			
		||||
                'positions' => [] // Will be populated later
 | 
			
		||||
            ];
 | 
			
		||||
            
 | 
			
		||||
            $response->getBody()->write(json_encode($data));
 | 
			
		||||
            return $response->withHeader('Content-Type', 'application/json');
 | 
			
		||||
        });
 | 
			
		||||
        
 | 
			
		||||
        // Tenant-specific user authentication routes
 | 
			
		||||
        $this->app->post('/auth/login', function (Request $request, Response $response, array $args) {
 | 
			
		||||
            $tenant = $request->getAttribute('tenant');
 | 
			
		||||
            $parsedBody = $request->getParsedBody();
 | 
			
		||||
            
 | 
			
		||||
            $email = $parsedBody['email'] ?? '';
 | 
			
		||||
            $password = $parsedBody['password'] ?? '';
 | 
			
		||||
            
 | 
			
		||||
            if (empty($email) || empty($password)) {
 | 
			
		||||
                $response->getBody()->write(json_encode(['error' => 'Email and password are required']));
 | 
			
		||||
                return $response->withStatus(400)->withHeader('Content-Type', 'application/json');
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            // Authenticate user
 | 
			
		||||
            $userModel = new \App\Models\User();
 | 
			
		||||
            $user = $userModel->authenticate($email, $password);
 | 
			
		||||
            
 | 
			
		||||
            if ($user && $user['tenant_id'] === $tenant['id']) {
 | 
			
		||||
                // For now, just return user info
 | 
			
		||||
                $response->getBody()->write(json_encode([
 | 
			
		||||
                    'user' => [
 | 
			
		||||
                        'id' => $user['id'],
 | 
			
		||||
                        'email' => $user['email'],
 | 
			
		||||
                        'first_name' => $user['first_name'],
 | 
			
		||||
                        'last_name' => $user['last_name'],
 | 
			
		||||
                        'role' => $user['role']
 | 
			
		||||
                    ]
 | 
			
		||||
                ]));
 | 
			
		||||
                return $response->withHeader('Content-Type', 'application/json');
 | 
			
		||||
            } else {
 | 
			
		||||
                $response->getBody()->write(json_encode(['error' => 'Invalid credentials']));
 | 
			
		||||
                return $response->withStatus(401)->withHeader('Content-Type', 'application/json');
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        
 | 
			
		||||
        // OIDC/Social login routes
 | 
			
		||||
        $this->app->get('/auth/oidc', [\App\Controllers\AuthController::class, 'redirectToOIDC']);
 | 
			
		||||
        $this->app->get('/auth/callback', [\App\Controllers\AuthController::class, 'handleOIDCCallback']);
 | 
			
		||||
        $this->app->get('/auth/google', [\App\Controllers\AuthController::class, 'redirectToGoogle']);
 | 
			
		||||
        $this->app->get('/auth/facebook', [\App\Controllers\AuthController::class, 'redirectToFacebook']);
 | 
			
		||||
        
 | 
			
		||||
        // More specific callback routes for social providers
 | 
			
		||||
        $this->app->get('/auth/google/callback', [\App\Controllers\AuthController::class, 'handleOIDCCallback']); // Reuse for now
 | 
			
		||||
        $this->app->get('/auth/facebook/callback', [\App\Controllers\AuthController::class, 'handleOIDCCallback']); // Reuse for now
 | 
			
		||||
        
 | 
			
		||||
        // Job seeker routes
 | 
			
		||||
        $this->app->get('/positions', [\App\Controllers\JobSeekerController::class, 'browsePositions']);
 | 
			
		||||
        $this->app->get('/positions/{id}', [\App\Controllers\JobSeekerController::class, 'getPosition']);
 | 
			
		||||
        $this->app->post('/positions/{id}/apply', [\App\Controllers\JobSeekerController::class, 'applyForPosition']);
 | 
			
		||||
        $this->app->get('/my/applications', [\App\Controllers\JobSeekerController::class, 'getMyApplications']);
 | 
			
		||||
        
 | 
			
		||||
        // Job provider routes
 | 
			
		||||
        $this->app->post('/positions', [\App\Controllers\JobProviderController::class, 'createPosition']);
 | 
			
		||||
        $this->app->put('/positions/{id}', [\App\Controllers\JobProviderController::class, 'updatePosition']);
 | 
			
		||||
        $this->delete('/positions/{id}', [\App\Controllers\JobProviderController::class, 'deletePosition']);
 | 
			
		||||
        $this->app->get('/positions/{id}/applications', [\App\Controllers\JobProviderController::class, 'getApplicationsForPosition']);
 | 
			
		||||
        $this->put('/applications/{id}', [\App\Controllers\JobProviderController::class, 'updateApplicationStatus']);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function run(): void
 | 
			
		||||
    {
 | 
			
		||||
        $this->app->run();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										68
									
								
								qwen/php/src/Auth/AuthService.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								qwen/php/src/Auth/AuthService.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,68 @@
 | 
			
		||||
<?php
 | 
			
		||||
// src/Auth/AuthService.php
 | 
			
		||||
namespace App\Auth;
 | 
			
		||||
 | 
			
		||||
use App\Models\User;
 | 
			
		||||
use Firebase\JWT\JWT;
 | 
			
		||||
use Firebase\JWT\Key;
 | 
			
		||||
 | 
			
		||||
class AuthService
 | 
			
		||||
{
 | 
			
		||||
    private $userModel;
 | 
			
		||||
    private $jwtSecret;
 | 
			
		||||
 | 
			
		||||
    public function __construct()
 | 
			
		||||
    {
 | 
			
		||||
        $this->userModel = new User();
 | 
			
		||||
        $this->jwtSecret = $_ENV['JWT_SECRET'] ?? 'default_secret_for_dev';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function createJWT(array $payload): string
 | 
			
		||||
    {
 | 
			
		||||
        $payload['iat'] = time();
 | 
			
		||||
        $payload['exp'] = time() + ($_ENV['SESSION_LIFETIME'] ?? 3600);
 | 
			
		||||
        
 | 
			
		||||
        return JWT::encode($payload, $this->jwtSecret, 'HS256');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function verifyJWT(string $token): ?array
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            $decoded = JWT::decode($token, new Key($this->jwtSecret, 'HS256'));
 | 
			
		||||
            return (array) $decoded;
 | 
			
		||||
        } catch (\Exception $e) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function createUserFromProvider(array $providerUser, string $provider, string $tenantId): string
 | 
			
		||||
    {
 | 
			
		||||
        // Check if user already exists with this provider ID
 | 
			
		||||
        $existingUser = $this->userModel->findByEmail($providerUser['email']);
 | 
			
		||||
        
 | 
			
		||||
        if ($existingUser) {
 | 
			
		||||
            // Update existing user with provider info if needed
 | 
			
		||||
            // For now, we'll just return the existing user ID
 | 
			
		||||
            return $existingUser['id'];
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Create a new user
 | 
			
		||||
        $userData = [
 | 
			
		||||
            'tenant_id' => $tenantId,
 | 
			
		||||
            'email' => $providerUser['email'],
 | 
			
		||||
            'password' => bin2hex(random_bytes(16)), // Placeholder password for OAuth users
 | 
			
		||||
            'first_name' => $providerUser['first_name'] ?? '',
 | 
			
		||||
            'last_name' => $providerUser['last_name'] ?? '',
 | 
			
		||||
            'role' => 'job_seeker', // Default role for new users
 | 
			
		||||
            'provider' => $provider,
 | 
			
		||||
            'provider_id' => $providerUser['id']
 | 
			
		||||
        ];
 | 
			
		||||
        
 | 
			
		||||
        return $this->userModel->create($userData);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getUserByProviderId(string $providerId, string $provider): ?array
 | 
			
		||||
    {
 | 
			
		||||
        return $this->userModel->findByProviderId($providerId, $provider);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										65
									
								
								qwen/php/src/Auth/OIDCProvider.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								qwen/php/src/Auth/OIDCProvider.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,65 @@
 | 
			
		||||
<?php
 | 
			
		||||
// src/Auth/OIDCProvider.php
 | 
			
		||||
namespace App\Auth;
 | 
			
		||||
 | 
			
		||||
use League\OAuth2\Client\Provider\AbstractProvider;
 | 
			
		||||
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
 | 
			
		||||
use League\OAuth2\Client\Token\AccessToken;
 | 
			
		||||
use League\OAuth2\Client\Tool\BearerAuthorizationTrait;
 | 
			
		||||
use Psr\Http\Message\ResponseInterface;
 | 
			
		||||
 | 
			
		||||
class OIDCProvider extends AbstractProvider
 | 
			
		||||
{
 | 
			
		||||
    use BearerAuthorizationTrait;
 | 
			
		||||
 | 
			
		||||
    public const ACCESS_TOKEN_RESOURCE_OWNER_ID = 'sub';
 | 
			
		||||
 | 
			
		||||
    protected $url;
 | 
			
		||||
    protected $issuer;
 | 
			
		||||
    protected $authorizationUrl;
 | 
			
		||||
    protected $tokenUrl;
 | 
			
		||||
    protected $userInfoUrl;
 | 
			
		||||
 | 
			
		||||
    public function __construct(array $options = [], array $collaborators = [])
 | 
			
		||||
    {
 | 
			
		||||
        parent::__construct($options, $collaborators);
 | 
			
		||||
 | 
			
		||||
        $this->issuer = $options['url'];
 | 
			
		||||
        $this->authorizationUrl = $options['authorization_url'] ?? $this->issuer . '/oauth/authorize';
 | 
			
		||||
        $this->tokenUrl = $options['token_url'] ?? $this->issuer . '/oauth/token';
 | 
			
		||||
        $this->userInfoUrl = $options['userinfo_url'] ?? $this->issuer . '/oauth/userinfo';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getBaseAuthorizationUrl(): string
 | 
			
		||||
    {
 | 
			
		||||
        return $this->authorizationUrl;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getBaseAccessTokenUrl(array $params): string
 | 
			
		||||
    {
 | 
			
		||||
        return $this->tokenUrl;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getResourceOwnerDetailsUrl(AccessToken $token): string
 | 
			
		||||
    {
 | 
			
		||||
        return $this->userInfoUrl;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function getDefaultScopes(): array
 | 
			
		||||
    {
 | 
			
		||||
        return ['openid', 'profile', 'email'];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function checkResponse(ResponseInterface $response, $data): void
 | 
			
		||||
    {
 | 
			
		||||
        if (!empty($data['error'])) {
 | 
			
		||||
            $message = $data['error'] . ': ' . ($data['error_description'] ?? '');
 | 
			
		||||
            throw new IdentityProviderException($message, $response->getStatusCode(), $response);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function createResourceOwner(array $response, AccessToken $token): OIDCResourceOwner
 | 
			
		||||
    {
 | 
			
		||||
        return new OIDCResourceOwner($response);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										45
									
								
								qwen/php/src/Auth/OIDCResourceOwner.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								qwen/php/src/Auth/OIDCResourceOwner.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,45 @@
 | 
			
		||||
<?php
 | 
			
		||||
// src/Auth/OIDCResourceOwner.php
 | 
			
		||||
namespace App\Auth;
 | 
			
		||||
 | 
			
		||||
use League\OAuth2\Client\Provider\ResourceOwnerInterface;
 | 
			
		||||
 | 
			
		||||
class OIDCResourceOwner implements ResourceOwnerInterface
 | 
			
		||||
{
 | 
			
		||||
    private $response;
 | 
			
		||||
 | 
			
		||||
    public function __construct(array $response)
 | 
			
		||||
    {
 | 
			
		||||
        $this->response = $response;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getId(): ?string
 | 
			
		||||
    {
 | 
			
		||||
        return $this->response['sub'] ?? null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function toArray(): array
 | 
			
		||||
    {
 | 
			
		||||
        return $this->response;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getEmail(): ?string
 | 
			
		||||
    {
 | 
			
		||||
        return $this->response['email'] ?? null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getName(): ?string
 | 
			
		||||
    {
 | 
			
		||||
        return $this->response['name'] ?? null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getFirstName(): ?string
 | 
			
		||||
    {
 | 
			
		||||
        return $this->response['given_name'] ?? null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getLastName(): ?string
 | 
			
		||||
    {
 | 
			
		||||
        return $this->response['family_name'] ?? null;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										190
									
								
								qwen/php/src/Controllers/AuthController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										190
									
								
								qwen/php/src/Controllers/AuthController.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,190 @@
 | 
			
		||||
<?php
 | 
			
		||||
// src/Controllers/AuthController.php
 | 
			
		||||
namespace App\Controllers;
 | 
			
		||||
 | 
			
		||||
use App\Auth\AuthService;
 | 
			
		||||
use App\Auth\OIDCProvider;
 | 
			
		||||
use App\Models\Tenant;
 | 
			
		||||
use App\Models\User;
 | 
			
		||||
use Psr\Http\Message\ResponseInterface;
 | 
			
		||||
use Psr\Http\Message\ServerRequestInterface;
 | 
			
		||||
use Slim\Psr7\Response;
 | 
			
		||||
 | 
			
		||||
class AuthController
 | 
			
		||||
{
 | 
			
		||||
    private $authService;
 | 
			
		||||
    private $tenantModel;
 | 
			
		||||
    private $ userModel;
 | 
			
		||||
 | 
			
		||||
    public function __construct()
 | 
			
		||||
    {
 | 
			
		||||
        $this->authService = new AuthService();
 | 
			
		||||
        $this->tenantModel = new Tenant();
 | 
			
		||||
        $this->userModel = new User();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function redirectToOIDC(ServerRequestInterface $request, ResponseInterface $response, array $args): ResponseInterface
 | 
			
		||||
    {
 | 
			
		||||
        $tenant = $request->getAttribute('tenant');
 | 
			
		||||
        
 | 
			
		||||
        $clientId = $_ENV['OIDC_CLIENT_ID'] ?? '';
 | 
			
		||||
        $clientSecret = $_ENV['OIDC_CLIENT_SECRET'] ?? '';
 | 
			
		||||
        $redirectUri = $_ENV['OIDC_REDIRECT_URI'] ?? '';
 | 
			
		||||
        $providerUrl = $_ENV['OIDC_PROVIDER_URL'] ?? '';
 | 
			
		||||
        
 | 
			
		||||
        if (empty($clientId) || empty($clientSecret) || empty($redirectUri) || empty($providerUrl)) {
 | 
			
		||||
            $response = new Response();
 | 
			
		||||
            $response->getBody()->write(json_encode(['error' => 'OIDC configuration not set']));
 | 
			
		||||
            return $response->withStatus(500)->withHeader('Content-Type', 'application/json');
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // In a full implementation, we would redirect the user to the OIDC provider
 | 
			
		||||
        // For now, we'll just return the URL that would be used
 | 
			
		||||
        $authUrl = $providerUrl . '/oauth/authorize?' . http_build_query([
 | 
			
		||||
            'client_id' => $clientId,
 | 
			
		||||
            'redirect_uri' => $redirectUri,
 | 
			
		||||
            'response_type' => 'code',
 | 
			
		||||
            'scope' => 'openid profile email',
 | 
			
		||||
            'state' => bin2hex(random_bytes(16)) // CSRF protection
 | 
			
		||||
        ]);
 | 
			
		||||
        
 | 
			
		||||
        // For this demo, we'll return the URL instead of redirecting
 | 
			
		||||
        $result = [
 | 
			
		||||
            'redirect_url' => $authUrl,
 | 
			
		||||
            'tenant' => $tenant['name'],
 | 
			
		||||
            'message' => 'Redirect to OIDC provider'
 | 
			
		||||
        ];
 | 
			
		||||
        
 | 
			
		||||
        $response->getBody()->write(json_encode($result));
 | 
			
		||||
        return $response->withHeader('Content-Type', 'application/json');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function handleOIDCCallback(ServerRequestInterface $request, ResponseInterface $response, array $args): ResponseInterface
 | 
			
		||||
    {
 | 
			
		||||
        $tenant = $request->getAttribute('tenant');
 | 
			
		||||
        $queryParams = $request->getQueryParams();
 | 
			
		||||
        $code = $queryParams['code'] ?? null;
 | 
			
		||||
        $state = $queryParams['state'] ?? null;
 | 
			
		||||
        
 | 
			
		||||
        if (!$code) {
 | 
			
		||||
            $response->getBody()->write(json_encode(['error' => 'Authorization code not provided']));
 | 
			
		||||
            return $response->withStatus(400)->withHeader('Content-Type', 'application/json');
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // In a full implementation, we would:
 | 
			
		||||
        // 1. Verify the state parameter for CSRF protection
 | 
			
		||||
        // 2. Exchange the authorization code for tokens
 | 
			
		||||
        // 3. Use the access token to retrieve user info
 | 
			
		||||
        // 4. Create or update the user in our database
 | 
			
		||||
        // 5. Generate a local JWT for our application
 | 
			
		||||
        
 | 
			
		||||
        // For this demo, we'll simulate the process
 | 
			
		||||
        $oidcUser = [
 | 
			
		||||
            'id' => 'oidc_user_id_' . bin2hex(random_bytes(8)),
 | 
			
		||||
            'email' => 'oidc_user@example.com',
 | 
			
		||||
            'first_name' => 'OIDC',
 | 
			
		||||
            'last_name' => 'User',
 | 
			
		||||
            'name' => 'OIDC User'
 | 
			
		||||
        ];
 | 
			
		||||
        
 | 
			
		||||
        // Create or update user in our database
 | 
			
		||||
        $userId = $this->authService->createUserFromProvider([
 | 
			
		||||
            'id' => $oidcUser['id'],
 | 
			
		||||
            'email' => $oidcUser['email'],
 | 
			
		||||
            'first_name' => $oidcUser['first_name'],
 | 
			
		||||
            'last_name' => $oidcUser['last_name']
 | 
			
		||||
        ], 'oidc', $tenant['id']);
 | 
			
		||||
        
 | 
			
		||||
        // Generate JWT for our application
 | 
			
		||||
        $jwt = $this->authService->createJWT([
 | 
			
		||||
            'user_id' => $userId,
 | 
			
		||||
            'tenant_id' => $tenant['id'],
 | 
			
		||||
            'email' => $oidcUser['email']
 | 
			
		||||
        ]);
 | 
			
		||||
        
 | 
			
		||||
        $result = [
 | 
			
		||||
            'message' => 'Successfully authenticated via OIDC',
 | 
			
		||||
            'user' => [
 | 
			
		||||
                'id' => $userId,
 | 
			
		||||
                'email' => $oidcUser['email'],
 | 
			
		||||
                'first_name' => $oidcUser['first_name'],
 | 
			
		||||
                'last_name' => $oidcUser['last_name']
 | 
			
		||||
            ],
 | 
			
		||||
            'token' => $jwt,
 | 
			
		||||
            'tenant' => $tenant['name']
 | 
			
		||||
        ];
 | 
			
		||||
        
 | 
			
		||||
        $response->getBody()->write(json_encode($result));
 | 
			
		||||
        return $response->withHeader('Content-Type', 'application/json');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function redirectToGoogle(ServerRequestInterface $request, ResponseInterface $response, array $args): ResponseInterface
 | 
			
		||||
    {
 | 
			
		||||
        $tenant = $request->getAttribute('tenant');
 | 
			
		||||
        
 | 
			
		||||
        $clientId = $_ENV['GOOGLE_CLIENT_ID'] ?? '';
 | 
			
		||||
        $clientSecret = $_ENV['GOOGLE_CLIENT_SECRET'] ?? '';
 | 
			
		||||
        $redirectUri = $_ENV['APP_URL'] . '/auth/google/callback';
 | 
			
		||||
        
 | 
			
		||||
        if (empty($clientId) || empty($clientSecret)) {
 | 
			
		||||
            $response = new Response();
 | 
			
		||||
            $response->getBody()->write(json_encode(['error' => 'Google OAuth configuration not set']));
 | 
			
		||||
            return $response->withStatus(500)->withHeader('Content-Type', 'application/json');
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // In a full implementation, we would redirect the user to Google OAuth
 | 
			
		||||
        // For now, we'll just return the URL that would be used
 | 
			
		||||
        $authUrl = 'https://accounts.google.com/o/oauth2/v2/auth?' . http_build_query([
 | 
			
		||||
            'client_id' => $clientId,
 | 
			
		||||
            'redirect_uri' => $redirectUri,
 | 
			
		||||
            'response_type' => 'code',
 | 
			
		||||
            'scope' => 'openid email profile',
 | 
			
		||||
            'state' => bin2hex(random_bytes(16)) // CSRF protection
 | 
			
		||||
        ]);
 | 
			
		||||
        
 | 
			
		||||
        // For this demo, we'll return the URL instead of redirecting
 | 
			
		||||
        $result = [
 | 
			
		||||
            'redirect_url' => $authUrl,
 | 
			
		||||
            'tenant' => $tenant['name'],
 | 
			
		||||
            'message' => 'Redirect to Google OAuth'
 | 
			
		||||
        ];
 | 
			
		||||
        
 | 
			
		||||
        $response->getBody()->write(json_encode($result));
 | 
			
		||||
        return $response->withHeader('Content-Type', 'application/json');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function redirectToFacebook(ServerRequestInterface $request, ResponseInterface $response, array $args): ResponseInterface
 | 
			
		||||
    {
 | 
			
		||||
        $tenant = $request->getAttribute('tenant');
 | 
			
		||||
        
 | 
			
		||||
        $clientId = $_ENV['FACEBOOK_CLIENT_ID'] ?? '';
 | 
			
		||||
        $clientSecret = $_ENV['FACEBOOK_CLIENT_SECRET'] ?? '';
 | 
			
		||||
        $redirectUri = $_ENV['APP_URL'] . '/auth/facebook/callback';
 | 
			
		||||
        
 | 
			
		||||
        if (empty($clientId) || empty($clientSecret)) {
 | 
			
		||||
            $response = new Response();
 | 
			
		||||
            $response->getBody()->write(json_encode(['error' => 'Facebook OAuth configuration not set']));
 | 
			
		||||
            return $response->withStatus(500)->withHeader('Content-Type', 'application/json');
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // In a full implementation, we would redirect the user to Facebook OAuth
 | 
			
		||||
        // For now, we'll just return the URL that would be used
 | 
			
		||||
        $authUrl = 'https://www.facebook.com/v17.0/dialog/oauth?' . http_build_query([
 | 
			
		||||
            'client_id' => $clientId,
 | 
			
		||||
            'redirect_uri' => $redirectUri,
 | 
			
		||||
            'response_type' => 'code',
 | 
			
		||||
            'scope' => 'email,public_profile',
 | 
			
		||||
            'state' => bin2hex(random_bytes(16)) // CSRF protection
 | 
			
		||||
        ]);
 | 
			
		||||
        
 | 
			
		||||
        // For this demo, we'll return the URL instead of redirecting
 | 
			
		||||
        $result = [
 | 
			
		||||
            'redirect_url' => $authUrl,
 | 
			
		||||
            'tenant' => $tenant['name'],
 | 
			
		||||
            'message' => 'Redirect to Facebook OAuth'
 | 
			
		||||
        ];
 | 
			
		||||
        
 | 
			
		||||
        $response->getBody()->write(json_encode($result));
 | 
			
		||||
        return $response->withHeader('Content-Type', 'application/json');
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										237
									
								
								qwen/php/src/Controllers/JobProviderController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										237
									
								
								qwen/php/src/Controllers/JobProviderController.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,237 @@
 | 
			
		||||
<?php
 | 
			
		||||
// src/Controllers/JobProviderController.php
 | 
			
		||||
namespace App\Controllers;
 | 
			
		||||
 | 
			
		||||
use App\Models\JobPosition;
 | 
			
		||||
use App\Models\ApplicationModel;
 | 
			
		||||
use App\Models\User;
 | 
			
		||||
use App\Auth\AuthService;
 | 
			
		||||
use Psr\Http\Message\ResponseInterface;
 | 
			
		||||
use Psr\Http\Message\ServerRequestInterface;
 | 
			
		||||
use Slim\Psr7\Response;
 | 
			
		||||
 | 
			
		||||
class JobProviderController
 | 
			
		||||
{
 | 
			
		||||
    private $jobPositionModel;
 | 
			
		||||
    private $applicationModel;
 | 
			
		||||
    private $userModel;
 | 
			
		||||
    private $authService;
 | 
			
		||||
 | 
			
		||||
    public function __construct()
 | 
			
		||||
    {
 | 
			
		||||
        $this->jobPositionModel = new JobPosition();
 | 
			
		||||
        $this->applicationModel = new ApplicationModel();
 | 
			
		||||
        $this->userModel = new User();
 | 
			
		||||
        $this->authService = new AuthService();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function createPosition(ServerRequestInterface $request, ResponseInterface $response, array $args): ResponseInterface
 | 
			
		||||
    {
 | 
			
		||||
        $tenant = $request->getAttribute('tenant');
 | 
			
		||||
        
 | 
			
		||||
        // For now, we'll assume the user is authenticated and get their ID from query params or headers
 | 
			
		||||
        // In a real app, this would be extracted from the JWT in an authentication middleware
 | 
			
		||||
        $userId = $_GET['user_id'] ?? null; // This is just for demo purposes
 | 
			
		||||
        
 | 
			
		||||
        if (!$userId) {
 | 
			
		||||
            $response->getBody()->write(json_encode(['error' => 'User ID is required']));
 | 
			
		||||
            return $response->withStatus(400)->withHeader('Content-Type', 'application/json');
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Verify the user exists and is part of the tenant
 | 
			
		||||
        $user = $this->userModel->findById($userId);
 | 
			
		||||
        
 | 
			
		||||
        if (!$user || $user['tenant_id'] !== $tenant['id']) {
 | 
			
		||||
            $response->getBody()->write(json_encode(['error' => 'User not found or does not belong to this tenant']));
 | 
			
		||||
            return $response->withStatus(404)->withHeader('Content-Type', 'application/json');
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $parsedBody = $request->getParsedBody();
 | 
			
		||||
        
 | 
			
		||||
        // Validate required fields
 | 
			
		||||
        $requiredFields = ['title', 'description'];
 | 
			
		||||
        foreach ($requiredFields as $field) {
 | 
			
		||||
            if (empty($parsedBody[$field])) {
 | 
			
		||||
                $response->getBody()->write(json_encode(['error' => "$field is required"]));
 | 
			
		||||
                return $response->withStatus(400)->withHeader('Content-Type', 'application/json');
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $positionData = [
 | 
			
		||||
            'tenant_id' => $tenant['id'],
 | 
			
		||||
            'title' => $parsedBody['title'],
 | 
			
		||||
            'description' => $parsedBody['description'],
 | 
			
		||||
            'location' => $parsedBody['location'] ?? '',
 | 
			
		||||
            'employment_type' => $parsedBody['employment_type'] ?? 'full_time',
 | 
			
		||||
            'salary_min' => $parsedBody['salary_min'] ?? null,
 | 
			
		||||
            'salary_max' => $parsedBody['salary_max'] ?? null,
 | 
			
		||||
            'posted_by' => $userId,
 | 
			
		||||
            'status' => $parsedBody['status'] ?? 'draft' // Default to draft, can be published later
 | 
			
		||||
        ];
 | 
			
		||||
        
 | 
			
		||||
        $positionId = $this->jobPositionModel->create($positionData);
 | 
			
		||||
        
 | 
			
		||||
        $result = [
 | 
			
		||||
            'message' => 'Job position created successfully',
 | 
			
		||||
            'position_id' => $positionId,
 | 
			
		||||
            'position' => $positionData,
 | 
			
		||||
            'tenant' => $tenant['name']
 | 
			
		||||
        ];
 | 
			
		||||
        
 | 
			
		||||
        $response->getBody()->write(json_encode($result));
 | 
			
		||||
        return $response->withHeader('Content-Type', 'application/json');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function updatePosition(ServerRequestInterface $request, ResponseInterface $response, array $args): ResponseInterface
 | 
			
		||||
    {
 | 
			
		||||
        $tenant = $request->getAttribute('tenant');
 | 
			
		||||
        $positionId = $args['id'] ?? null;
 | 
			
		||||
        
 | 
			
		||||
        if (!$positionId) {
 | 
			
		||||
            $response->getBody()->write(json_encode(['error' => 'Position ID is required']));
 | 
			
		||||
            return $response->withStatus(400)->withHeader('Content-Type', 'application/json');
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Verify the position belongs to this tenant
 | 
			
		||||
        $position = $this->jobPositionModel->findById($positionId, $tenant['id']);
 | 
			
		||||
        
 | 
			
		||||
        if (!$position) {
 | 
			
		||||
            $response->getBody()->write(json_encode(['error' => 'Position not found or does not belong to this tenant']));
 | 
			
		||||
            return $response->withStatus(404)->withHeader('Content-Type', 'application/json');
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $parsedBody = $request->getParsedBody();
 | 
			
		||||
        
 | 
			
		||||
        // For this demo, we'll just update the status
 | 
			
		||||
        if (isset($parsedBody['status'])) {
 | 
			
		||||
            $validStatuses = ['draft', 'published', 'closed'];
 | 
			
		||||
            if (!in_array($parsedBody['status'], $validStatuses)) {
 | 
			
		||||
                $response->getBody()->write(json_encode(['error' => 'Invalid status value']));
 | 
			
		||||
                return $response->withStatus(400)->withHeader('Content-Type', 'application/json');
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            $updated = $this->jobPositionModel->updateStatus($positionId, $parsedBody['status'], $tenant['id']);
 | 
			
		||||
            
 | 
			
		||||
            if (!$updated) {
 | 
			
		||||
                $response->getBody()->write(json_encode(['error' => 'Failed to update position status']));
 | 
			
		||||
                return $response->withStatus(500)->withHeader('Content-Type', 'application/json');
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            $result = [
 | 
			
		||||
                'message' => 'Position status updated successfully',
 | 
			
		||||
                'position_id' => $positionId,
 | 
			
		||||
                'new_status' => $parsedBody['status'],
 | 
			
		||||
                'tenant' => $tenant['name']
 | 
			
		||||
            ];
 | 
			
		||||
        } else {
 | 
			
		||||
            $result = [
 | 
			
		||||
                'message' => 'Nothing to update',
 | 
			
		||||
                'position_id' => $positionId,
 | 
			
		||||
                'tenant' => $tenant['name']
 | 
			
		||||
            ];
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $response->getBody()->write(json_encode($result));
 | 
			
		||||
        return $response->withHeader('Content-Type', 'application/json');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function deletePosition(ServerRequestInterface $request, ResponseInterface $response, array $args): ResponseInterface
 | 
			
		||||
    {
 | 
			
		||||
        $tenant = $request->getAttribute('tenant');
 | 
			
		||||
        $positionId = $args['id'] ?? null;
 | 
			
		||||
        
 | 
			
		||||
        if (!$positionId) {
 | 
			
		||||
            $response->getBody()->write(json_encode(['error' => 'Position ID is required']));
 | 
			
		||||
            return $response->withStatus(400)->withHeader('Content-Type', 'application/json');
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // In a real implementation, we would delete the position
 | 
			
		||||
        // For this demo, we'll just mark it as 'closed'
 | 
			
		||||
        $updated = $this->jobPositionModel->updateStatus($positionId, 'closed', $tenant['id']);
 | 
			
		||||
        
 | 
			
		||||
        if (!$updated) {
 | 
			
		||||
            $response->getBody()->write(json_encode(['error' => 'Position not found or does not belong to this tenant']));
 | 
			
		||||
            return $response->withStatus(404)->withHeader('Content-Type', 'application/json');
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $result = [
 | 
			
		||||
            'message' => 'Position closed successfully',
 | 
			
		||||
            'position_id' => $positionId,
 | 
			
		||||
            'tenant' => $tenant['name']
 | 
			
		||||
        ];
 | 
			
		||||
        
 | 
			
		||||
        $response->getBody()->write(json_encode($result));
 | 
			
		||||
        return $response->withHeader('Content-Type', 'application/json');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getApplicationsForPosition(ServerRequestInterface $request, ResponseInterface $response, array $args): ResponseInterface
 | 
			
		||||
    {
 | 
			
		||||
        $tenant = $request->getAttribute('tenant');
 | 
			
		||||
        $positionId = $args['id'] ?? null;
 | 
			
		||||
        
 | 
			
		||||
        if (!$positionId) {
 | 
			
		||||
            $response->getBody()->write(json_encode(['error' => 'Position ID is required']));
 | 
			
		||||
            return $response->withStatus(400)->withHeader('Content-Type', 'application/json');
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Verify the position belongs to this tenant
 | 
			
		||||
        $position = $this->jobPositionModel->findById($positionId, $tenant['id']);
 | 
			
		||||
        
 | 
			
		||||
        if (!$position) {
 | 
			
		||||
            $response->getBody()->write(json_encode(['error' => 'Position not found or does not belong to this tenant']));
 | 
			
		||||
            return $response->withStatus(404)->withHeader('Content-Type', 'application/json');
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $applications = $this->applicationModel->findByJobPosition($positionId, $tenant['id']);
 | 
			
		||||
        
 | 
			
		||||
        $result = [
 | 
			
		||||
            'applications' => $applications,
 | 
			
		||||
            'position' => $position,
 | 
			
		||||
            'tenant' => $tenant['name']
 | 
			
		||||
        ];
 | 
			
		||||
        
 | 
			
		||||
        $response->getBody()->write(json_encode($result));
 | 
			
		||||
        return $response->withHeader('Content-Type', 'application/json');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function updateApplicationStatus(ServerRequestInterface $request, ResponseInterface $response, array $args): ResponseInterface
 | 
			
		||||
    {
 | 
			
		||||
        $tenant = $request->getAttribute('tenant');
 | 
			
		||||
        $applicationId = $args['id'] ?? null;
 | 
			
		||||
        
 | 
			
		||||
        if (!$applicationId) {
 | 
			
		||||
            $response->getBody()->write(json_encode(['error' => 'Application ID is required']));
 | 
			
		||||
            return $response->withStatus(400)->withHeader('Content-Type', 'application/json');
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $parsedBody = $request->getParsedBody();
 | 
			
		||||
        
 | 
			
		||||
        if (!isset($parsedBody['status'])) {
 | 
			
		||||
            $response->getBody()->write(json_encode(['error' => 'Status is required']));
 | 
			
		||||
            return $response->withStatus(400)->withHeader('Content-Type', 'application/json');
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $validStatuses = ['submitted', 'under_review', 'accepted', 'rejected'];
 | 
			
		||||
        if (!in_array($parsedBody['status'], $validStatuses)) {
 | 
			
		||||
            $response->getBody()->write(json_encode(['error' => 'Invalid status value']));
 | 
			
		||||
            return $response->withStatus(400)->withHeader('Content-Type', 'application/json');
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $updated = $this->applicationModel->updateStatus($applicationId, $parsedBody['status'], $tenant['id']);
 | 
			
		||||
        
 | 
			
		||||
        if (!$updated) {
 | 
			
		||||
            $response->getBody()->write(json_encode(['error' => 'Application not found or does not belong to this tenant']));
 | 
			
		||||
            return $response->withStatus(404)->withHeader('Content-Type', 'application/json');
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $result = [
 | 
			
		||||
            'message' => 'Application status updated successfully',
 | 
			
		||||
            'application_id' => $applicationId,
 | 
			
		||||
            'new_status' => $parsedBody['status'],
 | 
			
		||||
            'tenant' => $tenant['name']
 | 
			
		||||
        ];
 | 
			
		||||
        
 | 
			
		||||
        $response->getBody()->write(json_encode($result));
 | 
			
		||||
        return $response->withHeader('Content-Type', 'application/json');
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										176
									
								
								qwen/php/src/Controllers/JobSeekerController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										176
									
								
								qwen/php/src/Controllers/JobSeekerController.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,176 @@
 | 
			
		||||
<?php
 | 
			
		||||
// src/Controllers/JobSeekerController.php
 | 
			
		||||
namespace App\Controllers;
 | 
			
		||||
 | 
			
		||||
use App\Models\JobPosition;
 | 
			
		||||
use App\Models\ApplicationModel;
 | 
			
		||||
use App\Models\User;
 | 
			
		||||
use App\Auth\AuthService;
 | 
			
		||||
use Psr\Http\Message\ResponseInterface;
 | 
			
		||||
use Psr\Http\Message\ServerRequestInterface;
 | 
			
		||||
use Slim\Psr7\Response;
 | 
			
		||||
 | 
			
		||||
class JobSeekerController
 | 
			
		||||
{
 | 
			
		||||
    private $jobPositionModel;
 | 
			
		||||
    private $applicationModel;
 | 
			
		||||
    private $authService;
 | 
			
		||||
 | 
			
		||||
    public function __construct()
 | 
			
		||||
    {
 | 
			
		||||
        $this->jobPositionModel = new JobPosition();
 | 
			
		||||
        $this->applicationModel = new ApplicationModel();
 | 
			
		||||
        $this->authService = new AuthService();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function browsePositions(ServerRequestInterface $request, ResponseInterface $response, array $args): ResponseInterface
 | 
			
		||||
    {
 | 
			
		||||
        $tenant = $request->getAttribute('tenant');
 | 
			
		||||
        
 | 
			
		||||
        // Get query parameters for filtering
 | 
			
		||||
        $queryParams = $request->getQueryParams();
 | 
			
		||||
        $location = $queryParams['location'] ?? null;
 | 
			
		||||
        $type = $queryParams['type'] ?? null;
 | 
			
		||||
        $search = $queryParams['search'] ?? null;
 | 
			
		||||
        
 | 
			
		||||
        // Get all published positions for this tenant
 | 
			
		||||
        $positions = $this->jobPositionModel->findByTenant($tenant['id'], 'published');
 | 
			
		||||
        
 | 
			
		||||
        // Apply filters if provided
 | 
			
		||||
        if ($location) {
 | 
			
		||||
            $positions = array_filter($positions, function($pos) use ($location) {
 | 
			
		||||
                return stripos($pos['location'], $location) !== false;
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if ($type) {
 | 
			
		||||
            $positions = array_filter($positions, function($pos) use ($type) {
 | 
			
		||||
                return stripos($pos['employment_type'], $type) !== false;
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if ($search) {
 | 
			
		||||
            $positions = array_filter($positions, function($pos) use ($search) {
 | 
			
		||||
                return stripos($pos['title'], $search) !== false || 
 | 
			
		||||
                       stripos($pos['description'], $search) !== false;
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $result = [
 | 
			
		||||
            'positions' => array_values($positions), // Re-index array after filtering
 | 
			
		||||
            'tenant' => $tenant['name'],
 | 
			
		||||
            'filters' => [
 | 
			
		||||
                'location' => $location,
 | 
			
		||||
                'type' => $type,
 | 
			
		||||
                'search' => $search
 | 
			
		||||
            ]
 | 
			
		||||
        ];
 | 
			
		||||
        
 | 
			
		||||
        $response->getBody()->write(json_encode($result));
 | 
			
		||||
        return $response->withHeader('Content-Type', 'application/json');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getPosition(ServerRequestInterface $request, ResponseInterface $response, array $args): ResponseInterface
 | 
			
		||||
    {
 | 
			
		||||
        $tenant = $request->getAttribute('tenant');
 | 
			
		||||
        $positionId = $args['id'] ?? null;
 | 
			
		||||
        
 | 
			
		||||
        if (!$positionId) {
 | 
			
		||||
            $response->getBody()->write(json_encode(['error' => 'Position ID is required']));
 | 
			
		||||
            return $response->withStatus(400)->withHeader('Content-Type', 'application/json');
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $position = $this->jobPositionModel->findById($positionId, $tenant['id']);
 | 
			
		||||
        
 | 
			
		||||
        if (!$position) {
 | 
			
		||||
            $response->getBody()->write(json_encode(['error' => 'Position not found']));
 | 
			
		||||
            return $response->withStatus(404)->withHeader('Content-Type', 'application/json');
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $result = [
 | 
			
		||||
            'position' => $position,
 | 
			
		||||
            'tenant' => $tenant['name']
 | 
			
		||||
        ];
 | 
			
		||||
        
 | 
			
		||||
        $response->getBody()->write(json_encode($result));
 | 
			
		||||
        return $response->withHeader('Content-Type', 'application/json');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function applyForPosition(ServerRequestInterface $request, ResponseInterface $response, array $args): ResponseInterface
 | 
			
		||||
    {
 | 
			
		||||
        $tenant = $request->getAttribute('tenant');
 | 
			
		||||
        $positionId = $args['id'] ?? null;
 | 
			
		||||
        
 | 
			
		||||
        // For now, we'll assume the user is authenticated and get their ID from query params or headers
 | 
			
		||||
        // In a real app, this would be extracted from the JWT in an authentication middleware
 | 
			
		||||
        $userId = $_GET['user_id'] ?? null; // This is just for demo purposes
 | 
			
		||||
        
 | 
			
		||||
        if (!$positionId) {
 | 
			
		||||
            $response->getBody()->write(json_encode(['error' => 'Position ID is required']));
 | 
			
		||||
            return $response->withStatus(400)->withHeader('Content-Type', 'application/json');
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (!$userId) {
 | 
			
		||||
            $response->getBody()->write(json_encode(['error' => 'User ID is required']));
 | 
			
		||||
            return $response->withStatus(400)->withHeader('Content-Type', 'application/json');
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Verify that the position exists and belongs to the current tenant
 | 
			
		||||
        $position = $this->jobPositionModel->findById($positionId, $tenant['id']);
 | 
			
		||||
        
 | 
			
		||||
        if (!$position) {
 | 
			
		||||
            $response->getBody()->write(json_encode(['error' => 'Position not found or does not belong to this tenant']));
 | 
			
		||||
            return $response->withStatus(404)->withHeader('Content-Type', 'application/json');
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $parsedBody = $request->getParsedBody();
 | 
			
		||||
        $resumePath = $parsedBody['resume_path'] ?? '';
 | 
			
		||||
        $coverLetter = $parsedBody['cover_letter'] ?? '';
 | 
			
		||||
        
 | 
			
		||||
        // Create application
 | 
			
		||||
        $applicationData = [
 | 
			
		||||
            'job_position_id' => $positionId,
 | 
			
		||||
            'applicant_id' => $userId,
 | 
			
		||||
            'resume_path' => $resumePath,
 | 
			
		||||
            'cover_letter' => $coverLetter,
 | 
			
		||||
            'status' => 'submitted'
 | 
			
		||||
        ];
 | 
			
		||||
        
 | 
			
		||||
        $applicationId = $this->applicationModel->create($applicationData);
 | 
			
		||||
        
 | 
			
		||||
        $result = [
 | 
			
		||||
            'message' => 'Application submitted successfully',
 | 
			
		||||
            'application_id' => $applicationId,
 | 
			
		||||
            'position' => $position,
 | 
			
		||||
            'tenant' => $tenant['name']
 | 
			
		||||
        ];
 | 
			
		||||
        
 | 
			
		||||
        $response->getBody()->write(json_encode($result));
 | 
			
		||||
        return $response->withHeader('Content-Type', 'application/json');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getMyApplications(ServerRequestInterface $request, ResponseInterface $response, array $args): ResponseInterface
 | 
			
		||||
    {
 | 
			
		||||
        $tenant = $request->getAttribute('tenant');
 | 
			
		||||
        
 | 
			
		||||
        // For now, we'll assume the user is authenticated and get their ID from query params or headers
 | 
			
		||||
        // In a real app, this would be extracted from the JWT in an authentication middleware
 | 
			
		||||
        $userId = $_GET['user_id'] ?? null; // This is just for demo purposes
 | 
			
		||||
        
 | 
			
		||||
        if (!$userId) {
 | 
			
		||||
            $response->getBody()->write(json_encode(['error' => 'User ID is required']));
 | 
			
		||||
            return $response->withStatus(400)->withHeader('Content-Type', 'application/json');
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $applications = $this->applicationModel->findByApplicant($userId, $tenant['id']);
 | 
			
		||||
        
 | 
			
		||||
        $result = [
 | 
			
		||||
            'applications' => $applications,
 | 
			
		||||
            'tenant' => $tenant['name'],
 | 
			
		||||
            'applicant_id' => $userId
 | 
			
		||||
        ];
 | 
			
		||||
        
 | 
			
		||||
        $response->getBody()->write(json_encode($result));
 | 
			
		||||
        return $response->withHeader('Content-Type', 'application/json');
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										36
									
								
								qwen/php/src/Database/DatabaseManager.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								qwen/php/src/Database/DatabaseManager.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
			
		||||
<?php
 | 
			
		||||
// src/Database/DatabaseManager.php
 | 
			
		||||
namespace App\Database;
 | 
			
		||||
 | 
			
		||||
use PDO;
 | 
			
		||||
use PDOException;
 | 
			
		||||
 | 
			
		||||
class DatabaseManager
 | 
			
		||||
{
 | 
			
		||||
    private static ?PDO $pdo = null;
 | 
			
		||||
 | 
			
		||||
    public static function connect(): PDO
 | 
			
		||||
    {
 | 
			
		||||
        if (self::$pdo === null) {
 | 
			
		||||
            $host = $_ENV['DB_HOST'] ?? 'localhost';
 | 
			
		||||
            $port = $_ENV['DB_PORT'] ?? '5432';
 | 
			
		||||
            $dbname = $_ENV['DB_NAME'] ?? 'moh_db';
 | 
			
		||||
            $username = $_ENV['DB_USER'] ?? 'moh_user';
 | 
			
		||||
            $password = $_ENV['DB_PASSWORD'] ?? 'moh_password';
 | 
			
		||||
 | 
			
		||||
            $dsn = "pgsql:host={$host};port={$port};dbname={$dbname}";
 | 
			
		||||
 | 
			
		||||
            try {
 | 
			
		||||
                self::$pdo = new PDO($dsn, $username, $password, [
 | 
			
		||||
                    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
 | 
			
		||||
                    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
 | 
			
		||||
                    PDO::ATTR_EMULATE_PREPARES => false,
 | 
			
		||||
                ]);
 | 
			
		||||
            } catch (PDOException $e) {
 | 
			
		||||
                throw new PDOException($e->getMessage(), (int)$e->getCode());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return self::$pdo;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										41
									
								
								qwen/php/src/Middleware/CorsMiddleware.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								qwen/php/src/Middleware/CorsMiddleware.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,41 @@
 | 
			
		||||
<?php
 | 
			
		||||
// src/Middleware/CorsMiddleware.php
 | 
			
		||||
namespace App\Middleware;
 | 
			
		||||
 | 
			
		||||
use Psr\Http\Message\ResponseInterface;
 | 
			
		||||
use Psr\Http\Message\ServerRequestInterface;
 | 
			
		||||
use Psr\Http\Server\MiddlewareInterface;
 | 
			
		||||
use Psr\Http\Server\RequestHandlerInterface;
 | 
			
		||||
 | 
			
		||||
class CorsMiddleware implements MiddlewareInterface
 | 
			
		||||
{
 | 
			
		||||
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
 | 
			
		||||
    {
 | 
			
		||||
        // Handle preflight requests
 | 
			
		||||
        if ($request->getMethod() === 'OPTIONS') {
 | 
			
		||||
            $response = new \Slim\Psr7\Response();
 | 
			
		||||
            return $this->addCorsHeaders($response);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $response = $handler->handle($request);
 | 
			
		||||
        return $this->addCorsHeaders($response);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private function addCorsHeaders(ResponseInterface $response): ResponseInterface
 | 
			
		||||
    {
 | 
			
		||||
        $allowedOrigins = $_ENV['ALLOWED_ORIGINS'] ?? 'http://localhost:3000,http://localhost:8080,https://merchantsOfHope.org';
 | 
			
		||||
        $origins = array_map('trim', explode(',', $allowedOrigins));
 | 
			
		||||
        
 | 
			
		||||
        $origin = $_SERVER['HTTP_ORIGIN'] ?? '';
 | 
			
		||||
        
 | 
			
		||||
        if (in_array($origin, $origins)) {
 | 
			
		||||
            $response = $response
 | 
			
		||||
                ->withHeader('Access-Control-Allow-Origin', $origin)
 | 
			
		||||
                ->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS')
 | 
			
		||||
                ->withHeader('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type, Accept, Origin, Authorization')
 | 
			
		||||
                ->withHeader('Access-Control-Allow-Credentials', 'true');
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return $response;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										28
									
								
								qwen/php/src/Middleware/SecurityMiddleware.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								qwen/php/src/Middleware/SecurityMiddleware.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
			
		||||
<?php
 | 
			
		||||
// src/Middleware/SecurityMiddleware.php
 | 
			
		||||
namespace App\Middleware;
 | 
			
		||||
 | 
			
		||||
use Psr\Http\Message\ResponseInterface;
 | 
			
		||||
use Psr\Http\Message\ServerRequestInterface;
 | 
			
		||||
use Psr\Http\Server\MiddlewareInterface;
 | 
			
		||||
use Psr\Http\Server\RequestHandlerInterface;
 | 
			
		||||
 | 
			
		||||
class SecurityMiddleware implements MiddlewareInterface
 | 
			
		||||
{
 | 
			
		||||
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
 | 
			
		||||
    {
 | 
			
		||||
        $response = $handler->handle($request);
 | 
			
		||||
        
 | 
			
		||||
        // Add security headers
 | 
			
		||||
        $response = $response
 | 
			
		||||
            ->withHeader('X-Frame-Options', 'DENY')
 | 
			
		||||
            ->withHeader('X-Content-Type-Options', 'nosniff')
 | 
			
		||||
            ->withHeader('X-XSS-Protection', '1; mode=block')
 | 
			
		||||
            ->withHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload')
 | 
			
		||||
            ->withHeader('Referrer-Policy', 'strict-origin-when-cross-origin')
 | 
			
		||||
            ->withHeader('Permissions-Policy', 'geolocation=(), microphone=(), camera=()')
 | 
			
		||||
            ->withHeader('Content-Security-Policy', "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self'; frame-ancestors 'none';");
 | 
			
		||||
        
 | 
			
		||||
        return $response;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										53
									
								
								qwen/php/src/Middleware/TenantMiddleware.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								qwen/php/src/Middleware/TenantMiddleware.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,53 @@
 | 
			
		||||
<?php
 | 
			
		||||
// src/Middleware/TenantMiddleware.php
 | 
			
		||||
namespace App\Middleware;
 | 
			
		||||
 | 
			
		||||
use App\Models\Tenant;
 | 
			
		||||
use Psr\Http\Message\ResponseInterface;
 | 
			
		||||
use Psr\Http\Message\ServerRequestInterface;
 | 
			
		||||
use Psr\Http\Server\MiddlewareInterface;
 | 
			
		||||
use Psr\Http\Server\RequestHandlerInterface;
 | 
			
		||||
 | 
			
		||||
class TenantMiddleware implements MiddlewareInterface
 | 
			
		||||
{
 | 
			
		||||
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
 | 
			
		||||
    {
 | 
			
		||||
        // Extract subdomain from the host
 | 
			
		||||
        $host = $request->getHeaderLine('Host');
 | 
			
		||||
        $subdomain = $this->extractSubdomain($host);
 | 
			
		||||
        
 | 
			
		||||
        // If no specific subdomain, assume the main site
 | 
			
		||||
        if (!$subdomain || $subdomain === 'localhost') {
 | 
			
		||||
            $subdomain = 'tsys'; // default tenant
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Find tenant by subdomain
 | 
			
		||||
        $tenantModel = new Tenant();
 | 
			
		||||
        $tenant = $tenantModel->findBySubdomain($subdomain);
 | 
			
		||||
        
 | 
			
		||||
        if (!$tenant) {
 | 
			
		||||
            // Handle case where tenant doesn't exist
 | 
			
		||||
            $response = new \Slim\Psr7\Response();
 | 
			
		||||
            $response->getBody()->write(json_encode(['error' => 'Tenant not found']));
 | 
			
		||||
            return $response->withStatus(404)->withHeader('Content-Type', 'application/json');
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Add tenant to request attributes for use in route handlers
 | 
			
		||||
        $request = $request->withAttribute('tenant', $tenant);
 | 
			
		||||
        
 | 
			
		||||
        return $handler->handle($request);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private function extractSubdomain(string $host): ?string
 | 
			
		||||
    {
 | 
			
		||||
        $hostParts = explode('.', $host);
 | 
			
		||||
        
 | 
			
		||||
        // For localhost or IP addresses, return as is
 | 
			
		||||
        if (count($hostParts) === 1 || filter_var($hostParts[0], FILTER_VALIDATE_IP)) {
 | 
			
		||||
            return $host;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Return the first part (subdomain)
 | 
			
		||||
        return $hostParts[0];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										95
									
								
								qwen/php/src/Models/ApplicationModel.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								qwen/php/src/Models/ApplicationModel.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,95 @@
 | 
			
		||||
<?php
 | 
			
		||||
// src/Models/ApplicationModel.php
 | 
			
		||||
namespace App\Models;
 | 
			
		||||
 | 
			
		||||
use App\Database\DatabaseManager;
 | 
			
		||||
use PDO;
 | 
			
		||||
 | 
			
		||||
class ApplicationModel
 | 
			
		||||
{
 | 
			
		||||
    private $db;
 | 
			
		||||
 | 
			
		||||
    public function __construct()
 | 
			
		||||
    {
 | 
			
		||||
        $this->db = DatabaseManager::connect();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function findById(string $id): ?array
 | 
			
		||||
    {
 | 
			
		||||
        $stmt = $this->db->prepare('SELECT * FROM applications WHERE id = :id');
 | 
			
		||||
        $stmt->bindParam(':id', $id);
 | 
			
		||||
        $stmt->execute();
 | 
			
		||||
 | 
			
		||||
        return $stmt->fetch() ?: null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function findByJobPosition(string $jobPositionId, string $tenantId): array
 | 
			
		||||
    {
 | 
			
		||||
        $stmt = $this->db->prepare('
 | 
			
		||||
            SELECT a.*, u.first_name, u.last_name, u.email 
 | 
			
		||||
            FROM applications a
 | 
			
		||||
            JOIN users u ON a.applicant_id = u.id
 | 
			
		||||
            JOIN job_positions jp ON a.job_position_id = jp.id
 | 
			
		||||
            WHERE a.job_position_id = :job_position_id AND jp.tenant_id = :tenant_id
 | 
			
		||||
            ORDER BY a.created_at DESC
 | 
			
		||||
        ');
 | 
			
		||||
        $stmt->bindParam(':job_position_id', $jobPositionId);
 | 
			
		||||
        $stmt->bindParam(':tenant_id', $tenantId);
 | 
			
		||||
        $stmt->execute();
 | 
			
		||||
 | 
			
		||||
        return $stmt->fetchAll();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function findByApplicant(string $applicantId, string $tenantId): array
 | 
			
		||||
    {
 | 
			
		||||
        $stmt = $this->db->prepare('
 | 
			
		||||
            SELECT a.*, jp.title as position_title 
 | 
			
		||||
            FROM applications a
 | 
			
		||||
            JOIN job_positions jp ON a.job_position_id = jp.id
 | 
			
		||||
            WHERE a.applicant_id = :applicant_id AND jp.tenant_id = :tenant_id
 | 
			
		||||
            ORDER BY a.created_at DESC
 | 
			
		||||
        ');
 | 
			
		||||
        $stmt->bindParam(':applicant_id', $applicantId);
 | 
			
		||||
        $stmt->bindParam(':tenant_id', $tenantId);
 | 
			
		||||
        $stmt->execute();
 | 
			
		||||
 | 
			
		||||
        return $stmt->fetchAll();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function create(array $data): string
 | 
			
		||||
    {
 | 
			
		||||
        $id = bin2hex(random_bytes(16)); // Simple ID generation for demo
 | 
			
		||||
        
 | 
			
		||||
        $stmt = $this->db->prepare('
 | 
			
		||||
            INSERT INTO applications (id, job_position_id, applicant_id, resume_path, cover_letter, status) 
 | 
			
		||||
            VALUES (:id, :job_position_id, :applicant_id, :resume_path, :cover_letter, :status)
 | 
			
		||||
            RETURNING id
 | 
			
		||||
        ');
 | 
			
		||||
        $stmt->bindParam(':id', $id);
 | 
			
		||||
        $stmt->bindParam(':job_position_id', $data['job_position_id']);
 | 
			
		||||
        $stmt->bindParam(':applicant_id', $data['applicant_id']);
 | 
			
		||||
        $stmt->bindParam(':resume_path', $data['resume_path']);
 | 
			
		||||
        $stmt->bindParam(':cover_letter', $data['cover_letter']);
 | 
			
		||||
        $stmt->bindParam(':status', $data['status']);
 | 
			
		||||
        
 | 
			
		||||
        $stmt->execute();
 | 
			
		||||
        $result = $stmt->fetch();
 | 
			
		||||
        
 | 
			
		||||
        return $result['id'];
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public function updateStatus(string $id, string $status, string $tenantId): bool
 | 
			
		||||
    {
 | 
			
		||||
        $stmt = $this->db->prepare('
 | 
			
		||||
            UPDATE applications a
 | 
			
		||||
            SET status = :status, updated_at = CURRENT_TIMESTAMP
 | 
			
		||||
            FROM job_positions jp
 | 
			
		||||
            WHERE a.id = :id AND a.job_position_id = jp.id AND jp.tenant_id = :tenant_id
 | 
			
		||||
        ');
 | 
			
		||||
        $stmt->bindParam(':id', $id);
 | 
			
		||||
        $stmt->bindParam(':status', $status);
 | 
			
		||||
        $stmt->bindParam(':tenant_id', $tenantId);
 | 
			
		||||
        
 | 
			
		||||
        return $stmt->execute();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										72
									
								
								qwen/php/src/Models/JobPosition.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								qwen/php/src/Models/JobPosition.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,72 @@
 | 
			
		||||
<?php
 | 
			
		||||
// src/Models/JobPosition.php
 | 
			
		||||
namespace App\Models;
 | 
			
		||||
 | 
			
		||||
use App\Database\DatabaseManager;
 | 
			
		||||
use PDO;
 | 
			
		||||
 | 
			
		||||
class JobPosition
 | 
			
		||||
{
 | 
			
		||||
    private $db;
 | 
			
		||||
 | 
			
		||||
    public function __construct()
 | 
			
		||||
    {
 | 
			
		||||
        $this->db = DatabaseManager::connect();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function findById(string $id, string $tenantId): ?array
 | 
			
		||||
    {
 | 
			
		||||
        $stmt = $this->db->prepare('SELECT * FROM job_positions WHERE id = :id AND tenant_id = :tenant_id');
 | 
			
		||||
        $stmt->bindParam(':id', $id);
 | 
			
		||||
        $stmt->bindParam(':tenant_id', $tenantId);
 | 
			
		||||
        $stmt->execute();
 | 
			
		||||
 | 
			
		||||
        return $stmt->fetch() ?: null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function findByTenant(string $tenantId, string $status = 'published'): array
 | 
			
		||||
    {
 | 
			
		||||
        $stmt = $this->db->prepare('SELECT * FROM job_positions WHERE tenant_id = :tenant_id AND status = :status ORDER BY created_at DESC');
 | 
			
		||||
        $stmt->bindParam(':tenant_id', $tenantId);
 | 
			
		||||
        $stmt->bindParam(':status', $status);
 | 
			
		||||
        $stmt->execute();
 | 
			
		||||
 | 
			
		||||
        return $stmt->fetchAll();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function create(array $data): string
 | 
			
		||||
    {
 | 
			
		||||
        $id = bin2hex(random_bytes(16)); // Simple ID generation for demo
 | 
			
		||||
        
 | 
			
		||||
        $stmt = $this->db->prepare('
 | 
			
		||||
            INSERT INTO job_positions (id, tenant_id, title, description, location, employment_type, salary_min, salary_max, posted_by, status) 
 | 
			
		||||
            VALUES (:id, :tenant_id, :title, :description, :location, :employment_type, :salary_min, :salary_max, :posted_by, :status)
 | 
			
		||||
            RETURNING id
 | 
			
		||||
        ');
 | 
			
		||||
        $stmt->bindParam(':id', $id);
 | 
			
		||||
        $stmt->bindParam(':tenant_id', $data['tenant_id']);
 | 
			
		||||
        $stmt->bindParam(':title', $data['title']);
 | 
			
		||||
        $stmt->bindParam(':description', $data['description']);
 | 
			
		||||
        $stmt->bindParam(':location', $data['location']);
 | 
			
		||||
        $stmt->bindParam(':employment_type', $data['employment_type']);
 | 
			
		||||
        $stmt->bindParam(':salary_min', $data['salary_min']);
 | 
			
		||||
        $stmt->bindParam(':salary_max', $data['salary_max']);
 | 
			
		||||
        $stmt->bindParam(':posted_by', $data['posted_by']);
 | 
			
		||||
        $stmt->bindParam(':status', $data['status']);
 | 
			
		||||
        
 | 
			
		||||
        $stmt->execute();
 | 
			
		||||
        $result = $stmt->fetch();
 | 
			
		||||
        
 | 
			
		||||
        return $result['id'];
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public function updateStatus(string $id, string $status, string $tenantId): bool
 | 
			
		||||
    {
 | 
			
		||||
        $stmt = $this->db->prepare('UPDATE job_positions SET status = :status, updated_at = CURRENT_TIMESTAMP WHERE id = :id AND tenant_id = :tenant_id');
 | 
			
		||||
        $stmt->bindParam(':id', $id);
 | 
			
		||||
        $stmt->bindParam(':status', $status);
 | 
			
		||||
        $stmt->bindParam(':tenant_id', $tenantId);
 | 
			
		||||
        
 | 
			
		||||
        return $stmt->execute();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										51
									
								
								qwen/php/src/Models/Tenant.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								qwen/php/src/Models/Tenant.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,51 @@
 | 
			
		||||
<?php
 | 
			
		||||
// src/Models/Tenant.php
 | 
			
		||||
namespace App\Models;
 | 
			
		||||
 | 
			
		||||
use App\Database\DatabaseManager;
 | 
			
		||||
use PDO;
 | 
			
		||||
 | 
			
		||||
class Tenant
 | 
			
		||||
{
 | 
			
		||||
    private $db;
 | 
			
		||||
 | 
			
		||||
    public function __construct()
 | 
			
		||||
    {
 | 
			
		||||
        $this->db = DatabaseManager::connect();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function findById(string $id): ?array
 | 
			
		||||
    {
 | 
			
		||||
        $stmt = $this->db->prepare('SELECT * FROM tenants WHERE id = :id');
 | 
			
		||||
        $stmt->bindParam(':id', $id);
 | 
			
		||||
        $stmt->execute();
 | 
			
		||||
 | 
			
		||||
        return $stmt->fetch() ?: null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function findBySubdomain(string $subdomain): ?array
 | 
			
		||||
    {
 | 
			
		||||
        $stmt = $this->db->prepare('SELECT * FROM tenants WHERE subdomain = :subdomain');
 | 
			
		||||
        $stmt->bindParam(':subdomain', $subdomain);
 | 
			
		||||
        $stmt->execute();
 | 
			
		||||
 | 
			
		||||
        return $stmt->fetch() ?: null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function create(array $data): string
 | 
			
		||||
    {
 | 
			
		||||
        $id = bin2hex(random_bytes(16)); // Simple ID generation for demo
 | 
			
		||||
        $stmt = $this->db->prepare('
 | 
			
		||||
            INSERT INTO tenants (id, name, subdomain) 
 | 
			
		||||
            VALUES (:id, :name, :subdomain)
 | 
			
		||||
            RETURNING id
 | 
			
		||||
        ');
 | 
			
		||||
        $stmt->bindParam(':id', $id);
 | 
			
		||||
        $stmt->bindParam(':name', $data['name']);
 | 
			
		||||
        $stmt->bindParam(':subdomain', $data['subdomain']);
 | 
			
		||||
        $stmt->execute();
 | 
			
		||||
 | 
			
		||||
        $result = $stmt->fetch();
 | 
			
		||||
        return $result['id'];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										81
									
								
								qwen/php/src/Models/User.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								qwen/php/src/Models/User.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,81 @@
 | 
			
		||||
<?php
 | 
			
		||||
// src/Models/User.php
 | 
			
		||||
namespace App\Models;
 | 
			
		||||
 | 
			
		||||
use App\Database\DatabaseManager;
 | 
			
		||||
use PDO;
 | 
			
		||||
 | 
			
		||||
class User
 | 
			
		||||
{
 | 
			
		||||
    private $db;
 | 
			
		||||
 | 
			
		||||
    public function __construct()
 | 
			
		||||
    {
 | 
			
		||||
        $this->db = DatabaseManager::connect();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function findById(string $id): ?array
 | 
			
		||||
    {
 | 
			
		||||
        $stmt = $this->db->prepare('SELECT * FROM users WHERE id = :id');
 | 
			
		||||
        $stmt->bindParam(':id', $id);
 | 
			
		||||
        $stmt->execute();
 | 
			
		||||
 | 
			
		||||
        return $stmt->fetch() ?: null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function findByEmail(string $email): ?array
 | 
			
		||||
    {
 | 
			
		||||
        $stmt = $this->db->prepare('SELECT * FROM users WHERE email = :email');
 | 
			
		||||
        $stmt->bindParam(':email', $email);
 | 
			
		||||
        $stmt->execute();
 | 
			
		||||
 | 
			
		||||
        return $stmt->fetch() ?: null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function create(array $data): string
 | 
			
		||||
    {
 | 
			
		||||
        $id = bin2hex(random_bytes(16)); // Simple ID generation for demo
 | 
			
		||||
        $hashedPassword = password_hash($data['password'], PASSWORD_DEFAULT);
 | 
			
		||||
        
 | 
			
		||||
        $stmt = $this->db->prepare('
 | 
			
		||||
            INSERT INTO users (id, tenant_id, email, password_hash, first_name, last_name, role, provider, provider_id) 
 | 
			
		||||
            VALUES (:id, :tenant_id, :email, :password_hash, :first_name, :last_name, :role, :provider, :provider_id)
 | 
			
		||||
            RETURNING id
 | 
			
		||||
        ');
 | 
			
		||||
        $stmt->bindParam(':id', $id);
 | 
			
		||||
        $stmt->bindParam(':tenant_id', $data['tenant_id']);
 | 
			
		||||
        $stmt->bindParam(':email', $data['email']);
 | 
			
		||||
        $stmt->bindParam(':password_hash', $hashedPassword);
 | 
			
		||||
        $stmt->bindParam(':first_name', $data['first_name']);
 | 
			
		||||
        $stmt->bindParam(':last_name', $data['last_name']);
 | 
			
		||||
        $stmt->bindParam(':role', $data['role']);
 | 
			
		||||
        $stmt->bindParam(':provider', $data['provider']);
 | 
			
		||||
        $stmt->bindParam(':provider_id', $data['provider_id']);
 | 
			
		||||
        
 | 
			
		||||
        $stmt->execute();
 | 
			
		||||
        $result = $stmt->fetch();
 | 
			
		||||
        
 | 
			
		||||
        return $result['id'];
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public function authenticate(string $email, string $password): ?array
 | 
			
		||||
    {
 | 
			
		||||
        $user = $this->findByEmail($email);
 | 
			
		||||
        
 | 
			
		||||
        if ($user && password_verify($password, $user['password_hash'])) {
 | 
			
		||||
            return $user;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public function findByProviderId(string $providerId, string $provider): ?array
 | 
			
		||||
    {
 | 
			
		||||
        $stmt = $this->db->prepare('SELECT * FROM users WHERE provider_id = :provider_id AND provider = :provider');
 | 
			
		||||
        $stmt->bindParam(':provider_id', $providerId);
 | 
			
		||||
        $stmt->bindParam(':provider', $provider);
 | 
			
		||||
        $stmt->execute();
 | 
			
		||||
 | 
			
		||||
        return $stmt->fetch() ?: null;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										46
									
								
								qwen/php/src/Utils/Validator.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								qwen/php/src/Utils/Validator.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,46 @@
 | 
			
		||||
<?php
 | 
			
		||||
// src/Utils/Validator.php
 | 
			
		||||
namespace App\Utils;
 | 
			
		||||
 | 
			
		||||
class Validator
 | 
			
		||||
{
 | 
			
		||||
    public static function validateEmail(string $email): bool
 | 
			
		||||
    {
 | 
			
		||||
        return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function validateRequired(array $data, array $requiredFields): array
 | 
			
		||||
    {
 | 
			
		||||
        $errors = [];
 | 
			
		||||
        
 | 
			
		||||
        foreach ($requiredFields as $field) {
 | 
			
		||||
            if (!isset($data[$field]) || trim($data[$field]) === '') {
 | 
			
		||||
                $errors[] = "$field is required";
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return $errors;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function sanitizeString(string $string): string
 | 
			
		||||
    {
 | 
			
		||||
        return htmlspecialchars(strip_tags(trim($string)), ENT_QUOTES, 'UTF-8');
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function validateUrl(string $url): bool
 | 
			
		||||
    {
 | 
			
		||||
        return filter_var($url, FILTER_VALIDATE_URL) !== false;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function validateLength(string $string, int $min, int $max): bool
 | 
			
		||||
    {
 | 
			
		||||
        $length = strlen($string);
 | 
			
		||||
        return $length >= $min && $length <= $max;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function validateDate(string $date): bool
 | 
			
		||||
    {
 | 
			
		||||
        $d = DateTime::createFromFormat('Y-m-d', $date);
 | 
			
		||||
        return $d && $d->format('Y-m-d') === $date;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user