create-prisma-php-app 4.4.4-beta → 4.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bootstrap.php +580 -297
- package/dist/index.js +2 -2
- package/dist/prisma-php.js +2 -2
- package/dist/public/.htaccess +1 -1
- package/dist/public/assets/images/prisma-php.svg +146 -146
- package/dist/public/js/pp-reactive-v1.js +1 -1
- package/dist/settings/bs-config.ts +156 -18
- package/dist/settings/project-name.ts +50 -25
- package/dist/settings/vite-plugins/generate-global-types.ts +301 -0
- package/dist/src/Lib/Auth/Auth.php +92 -64
- package/dist/src/Lib/Auth/AuthConfig.php +1 -0
- package/dist/src/Lib/MCP/mcp-server.php +8 -7
- package/dist/src/Lib/Middleware/AuthMiddleware.php +31 -15
- package/dist/src/Lib/Middleware/CorsMiddleware.php +8 -6
- package/dist/src/Lib/Websocket/ConnectionManager.php +3 -3
- package/dist/src/Lib/Websocket/websocket-server.php +8 -7
- package/dist/src/app/index.php +2 -2
- package/dist/src/app/layout.php +1 -1
- package/dist/src/app/not-found.php +2 -2
- package/dist/tsconfig.json +1 -1
- package/dist/vite.config.ts +25 -3
- package/package.json +4 -4
|
@@ -15,11 +15,12 @@ use PP\Request;
|
|
|
15
15
|
use Exception;
|
|
16
16
|
use InvalidArgumentException;
|
|
17
17
|
use ArrayObject;
|
|
18
|
+
use PP\Env;
|
|
18
19
|
|
|
19
20
|
class Auth
|
|
20
21
|
{
|
|
21
22
|
public const PAYLOAD_NAME = 'payload_name_8639D';
|
|
22
|
-
public const ROLE_NAME = '';
|
|
23
|
+
public const ROLE_NAME = 'role';
|
|
23
24
|
public const PAYLOAD_SESSION_KEY = 'payload_session_key_2183A';
|
|
24
25
|
|
|
25
26
|
public static string $cookieName = '';
|
|
@@ -27,23 +28,14 @@ class Auth
|
|
|
27
28
|
private static ?Auth $instance = null;
|
|
28
29
|
private const PPAUTH = 'ppauth';
|
|
29
30
|
private string $secretKey;
|
|
30
|
-
private string $defaultTokenValidity =
|
|
31
|
+
private string $defaultTokenValidity = AuthConfig::DEFAULT_TOKEN_VALIDITY;
|
|
31
32
|
|
|
32
|
-
/**
|
|
33
|
-
* Private constructor to prevent direct instantiation.
|
|
34
|
-
* Use Auth::getInstance() to get the singleton instance.
|
|
35
|
-
*/
|
|
36
33
|
private function __construct()
|
|
37
34
|
{
|
|
38
|
-
$this->secretKey =
|
|
35
|
+
$this->secretKey = Env::string('AUTH_SECRET', 'CD24eEv4qbsC5LOzqeaWbcr58mBMSvA4Mkii8GjRiHkt');
|
|
39
36
|
self::$cookieName = self::getCookieName();
|
|
40
37
|
}
|
|
41
38
|
|
|
42
|
-
/**
|
|
43
|
-
* Returns the singleton instance of the Auth class.
|
|
44
|
-
*
|
|
45
|
-
* @return Auth The singleton instance.
|
|
46
|
-
*/
|
|
47
39
|
public static function getInstance(): Auth
|
|
48
40
|
{
|
|
49
41
|
if (self::$instance === null) {
|
|
@@ -53,33 +45,20 @@ class Auth
|
|
|
53
45
|
}
|
|
54
46
|
|
|
55
47
|
/**
|
|
56
|
-
* Authenticates a user and generates a JWT
|
|
57
|
-
*
|
|
58
|
-
* the token's expiration time, sets the necessary payload, and encodes it into a JWT.
|
|
59
|
-
* If possible (HTTP headers not yet sent), it also sets cookies with the JWT for client-side storage.
|
|
48
|
+
* Authenticates a user and generates a JWT.
|
|
49
|
+
* Optionally redirects the user to a default or custom URL.
|
|
60
50
|
*
|
|
61
|
-
* @param mixed $data User data
|
|
62
|
-
*
|
|
63
|
-
* @param string
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
51
|
+
* @param mixed $data User data (string or AuthRole).
|
|
52
|
+
* @param string|null $tokenValidity Duration token is valid for (e.g., '1h'). Default is '1h'.
|
|
53
|
+
* @param bool|string $redirect
|
|
54
|
+
* - If `false` (default): No redirect occurs; returns the JWT.
|
|
55
|
+
* - If `true`: Redirects to `AuthConfig::DEFAULT_SIGNIN_REDIRECT`.
|
|
56
|
+
* - If `string`: Redirects to the specified URL (e.g., '/dashboard').
|
|
67
57
|
*
|
|
68
58
|
* @return string Returns the encoded JWT as a string.
|
|
69
|
-
*
|
|
70
|
-
* @throws InvalidArgumentException Thrown if the secret key is not set or if the duration format is invalid.
|
|
71
|
-
*
|
|
72
|
-
* Example:
|
|
73
|
-
* $auth = Auth::getInstance();
|
|
74
|
-
* $auth->setSecretKey('your_secret_key');
|
|
75
|
-
* try {
|
|
76
|
-
* $jwt = $auth->signIn('Admin', '1h');
|
|
77
|
-
* echo "JWT: " . $jwt;
|
|
78
|
-
* } catch (InvalidArgumentException $e) {
|
|
79
|
-
* echo "Error: " . $e->getMessage();
|
|
80
|
-
* }
|
|
59
|
+
* @throws InvalidArgumentException
|
|
81
60
|
*/
|
|
82
|
-
public function signIn($data, ?string $tokenValidity = null): string
|
|
61
|
+
public function signIn($data, ?string $tokenValidity = null, bool|string $redirect = false): string
|
|
83
62
|
{
|
|
84
63
|
if (!$this->secretKey) {
|
|
85
64
|
throw new InvalidArgumentException("Secret key is required for authentication.");
|
|
@@ -96,14 +75,20 @@ class Auth
|
|
|
96
75
|
'exp' => $expirationTime,
|
|
97
76
|
];
|
|
98
77
|
|
|
99
|
-
// Set the payload in the session
|
|
100
78
|
$_SESSION[self::PAYLOAD_SESSION_KEY] = $payload;
|
|
101
79
|
|
|
102
|
-
// Encode the JWT
|
|
103
80
|
$jwt = JWT::encode($payload, $this->secretKey, 'HS256');
|
|
104
81
|
|
|
105
82
|
if (!headers_sent()) {
|
|
106
83
|
$this->setCookies($jwt, $expirationTime);
|
|
84
|
+
|
|
85
|
+
$this->rotateCsrfToken();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if ($redirect === true) {
|
|
89
|
+
Request::redirect(AuthConfig::DEFAULT_SIGNIN_REDIRECT);
|
|
90
|
+
} elseif (is_string($redirect) && !empty($redirect)) {
|
|
91
|
+
Request::redirect($redirect);
|
|
107
92
|
}
|
|
108
93
|
|
|
109
94
|
return $jwt;
|
|
@@ -184,21 +169,14 @@ class Auth
|
|
|
184
169
|
public function verifyToken(?string $jwt): ?object
|
|
185
170
|
{
|
|
186
171
|
try {
|
|
187
|
-
if (!$jwt)
|
|
188
|
-
return null;
|
|
189
|
-
}
|
|
172
|
+
if (!$jwt) return null;
|
|
190
173
|
|
|
191
174
|
$token = JWT::decode($jwt, new Key($this->secretKey, 'HS256'));
|
|
192
175
|
|
|
193
|
-
if (empty($token->{Auth::PAYLOAD_NAME}))
|
|
194
|
-
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
if (isset($token->exp) && time() >= $token->exp) {
|
|
198
|
-
return null;
|
|
199
|
-
}
|
|
176
|
+
if (empty($token->{Auth::PAYLOAD_NAME})) return null;
|
|
177
|
+
if (isset($token->exp) && time() >= $token->exp) return null;
|
|
200
178
|
|
|
201
|
-
return $token;
|
|
179
|
+
return $token->{Auth::PAYLOAD_NAME};
|
|
202
180
|
} catch (Exception) {
|
|
203
181
|
return null;
|
|
204
182
|
}
|
|
@@ -211,9 +189,9 @@ class Auth
|
|
|
211
189
|
*
|
|
212
190
|
* @param string $jwt The JWT token to refresh.
|
|
213
191
|
* @param string|null $tokenValidity Optional parameter specifying the duration the token is valid for (e.g., '10m', '1h').
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
*
|
|
192
|
+
* If null, the default validity period set in the class property is used.
|
|
193
|
+
* The format should be a number followed by a time unit ('s' for seconds, 'm' for minutes,
|
|
194
|
+
* 'h' for hours, 'd' for days), and this is parsed to calculate the exact expiration time.
|
|
217
195
|
*
|
|
218
196
|
* @return string Returns the refreshed JWT as a string.
|
|
219
197
|
*
|
|
@@ -221,16 +199,20 @@ class Auth
|
|
|
221
199
|
*/
|
|
222
200
|
public function refreshToken(string $jwt, ?string $tokenValidity = null): string
|
|
223
201
|
{
|
|
224
|
-
$
|
|
202
|
+
$decodedData = $this->verifyToken($jwt);
|
|
225
203
|
|
|
226
|
-
if (!$
|
|
204
|
+
if (!$decodedData) {
|
|
227
205
|
throw new InvalidArgumentException("Invalid token.");
|
|
228
206
|
}
|
|
229
207
|
|
|
230
208
|
$expirationTime = $this->calculateExpirationTime($tokenValidity ?? $this->defaultTokenValidity);
|
|
231
209
|
|
|
232
|
-
$
|
|
233
|
-
|
|
210
|
+
$payload = [
|
|
211
|
+
self::PAYLOAD_NAME => $decodedData,
|
|
212
|
+
'exp' => $expirationTime,
|
|
213
|
+
];
|
|
214
|
+
|
|
215
|
+
$newJwt = JWT::encode($payload, $this->secretKey, 'HS256');
|
|
234
216
|
|
|
235
217
|
if (!headers_sent()) {
|
|
236
218
|
$this->setCookies($newJwt, $expirationTime);
|
|
@@ -239,18 +221,62 @@ class Auth
|
|
|
239
221
|
return $newJwt;
|
|
240
222
|
}
|
|
241
223
|
|
|
224
|
+
/**
|
|
225
|
+
* Refreshes the current user's session if the provided User ID matches the currently authenticated user.
|
|
226
|
+
* This allows silent updates of permissions or profile data without requiring a logout.
|
|
227
|
+
* @param string $targetUserId The ID of the user being updated.
|
|
228
|
+
* @param mixed $newUserData The fresh user object (e.g. from Prisma) to replace the session payload.
|
|
229
|
+
* @return bool Returns true if the session was updated, false if the IDs did not match or no session exists.
|
|
230
|
+
*/
|
|
231
|
+
public function refreshUserSession(string $targetUserId, mixed $newUserData): bool
|
|
232
|
+
{
|
|
233
|
+
$currentUser = $this->getPayload();
|
|
234
|
+
|
|
235
|
+
if ($currentUser && isset($currentUser->id) && $currentUser->id === $targetUserId) {
|
|
236
|
+
$this->signIn($newUserData, null, false);
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
|
|
242
243
|
protected function setCookies(string $jwt, int $expirationTime)
|
|
243
244
|
{
|
|
244
245
|
if (!headers_sent()) {
|
|
245
246
|
setcookie(self::$cookieName, $jwt, [
|
|
246
247
|
'expires' => $expirationTime,
|
|
247
|
-
'path' => '/',
|
|
248
|
-
'domain' => '',
|
|
249
|
-
'secure' => true,
|
|
250
|
-
'httponly' => true,
|
|
251
|
-
'samesite' => 'Lax',
|
|
248
|
+
'path' => '/',
|
|
249
|
+
'domain' => '',
|
|
250
|
+
'secure' => true,
|
|
251
|
+
'httponly' => true,
|
|
252
|
+
'samesite' => 'Lax',
|
|
253
|
+
]);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
public function rotateCsrfToken(): void
|
|
258
|
+
{
|
|
259
|
+
$secret = Env::string('FUNCTION_CALL_SECRET', '');
|
|
260
|
+
|
|
261
|
+
if (empty($secret)) {
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
$nonce = bin2hex(random_bytes(16));
|
|
266
|
+
$signature = hash_hmac('sha256', $nonce, $secret);
|
|
267
|
+
$token = $nonce . '.' . $signature;
|
|
268
|
+
|
|
269
|
+
if (!headers_sent()) {
|
|
270
|
+
setcookie('prisma_php_csrf', $token, [
|
|
271
|
+
'expires' => time() + 3600, // 1 hour validity
|
|
272
|
+
'path' => '/',
|
|
273
|
+
'secure' => true,
|
|
274
|
+
'httponly' => false, // Must be FALSE so client JS can read it
|
|
275
|
+
'samesite' => 'Lax',
|
|
252
276
|
]);
|
|
253
277
|
}
|
|
278
|
+
|
|
279
|
+
$_COOKIE['prisma_php_csrf'] = $token;
|
|
254
280
|
}
|
|
255
281
|
|
|
256
282
|
/**
|
|
@@ -260,7 +286,7 @@ class Auth
|
|
|
260
286
|
* @param string|null $redirect Optional parameter specifying the URL to redirect to after logging out.
|
|
261
287
|
*
|
|
262
288
|
* Example:
|
|
263
|
-
*
|
|
289
|
+
* $auth = Auth::getInstance();
|
|
264
290
|
* $auth->signOut('/login');
|
|
265
291
|
*
|
|
266
292
|
* @return void
|
|
@@ -276,6 +302,8 @@ class Auth
|
|
|
276
302
|
unset($_SESSION[self::PAYLOAD_SESSION_KEY]);
|
|
277
303
|
}
|
|
278
304
|
|
|
305
|
+
$this->rotateCsrfToken();
|
|
306
|
+
|
|
279
307
|
if ($redirect) {
|
|
280
308
|
Request::redirect($redirect);
|
|
281
309
|
}
|
|
@@ -342,8 +370,8 @@ class Auth
|
|
|
342
370
|
* @param mixed ...$providers An array of provider objects such as GoogleProvider or GithubProvider.
|
|
343
371
|
*
|
|
344
372
|
* Example:
|
|
345
|
-
*
|
|
346
|
-
*
|
|
373
|
+
* $auth = Auth::getInstance();
|
|
374
|
+
* $auth->authProviders(new GoogleProvider('client_id', 'client_secret', 'redirect_uri'));
|
|
347
375
|
*/
|
|
348
376
|
public function authProviders(...$providers)
|
|
349
377
|
{
|
|
@@ -519,7 +547,7 @@ class Auth
|
|
|
519
547
|
|
|
520
548
|
private static function getCookieName(): string
|
|
521
549
|
{
|
|
522
|
-
$authCookieName =
|
|
550
|
+
$authCookieName = Env::string('AUTH_COOKIE_NAME', 'auth_cookie_name_d36e5');
|
|
523
551
|
return strtolower(preg_replace('/\s+/', '_', trim($authCookieName)));
|
|
524
552
|
}
|
|
525
553
|
}
|
|
@@ -23,6 +23,7 @@ final class AuthConfig
|
|
|
23
23
|
public const IS_ROLE_BASE = false;
|
|
24
24
|
public const IS_TOKEN_AUTO_REFRESH = false;
|
|
25
25
|
public const IS_ALL_ROUTES_PRIVATE = false;
|
|
26
|
+
public const DEFAULT_TOKEN_VALIDITY = "1h"; // Default to 1 hour
|
|
26
27
|
|
|
27
28
|
/**
|
|
28
29
|
* This is the (default) option for authentication. If IS_ALL_ROUTES_PRIVATE is set to false,
|
|
@@ -12,20 +12,21 @@ use Dotenv\Dotenv;
|
|
|
12
12
|
use PhpMcp\Server\Server;
|
|
13
13
|
use PhpMcp\Server\Transports\StreamableHttpServerTransport;
|
|
14
14
|
use Throwable;
|
|
15
|
+
use PP\Env;
|
|
15
16
|
|
|
16
17
|
// ── Load .env (optional) and timezone ──────────────────────────────────────────
|
|
17
18
|
if (file_exists(DOCUMENT_PATH . '/.env')) {
|
|
18
19
|
Dotenv::createImmutable(DOCUMENT_PATH)->safeLoad();
|
|
19
20
|
}
|
|
20
|
-
date_default_timezone_set(
|
|
21
|
+
date_default_timezone_set(Env::string('APP_TIMEZONE', 'UTC'));
|
|
21
22
|
|
|
22
23
|
// ── Resolve settings (with sane defaults) ─────────────────────────────────────
|
|
23
|
-
$appName =
|
|
24
|
-
$appVersion =
|
|
25
|
-
$host =
|
|
26
|
-
$port =
|
|
27
|
-
$prefix = trim(
|
|
28
|
-
$enableJson =
|
|
24
|
+
$appName = Env::string('MCP_NAME', 'prisma-php-mcp');
|
|
25
|
+
$appVersion = Env::string('MCP_VERSION', '0.0.1');
|
|
26
|
+
$host = Env::string('MCP_HOST', '127.0.0.1');
|
|
27
|
+
$port = Env::int('MCP_PORT', 4000);
|
|
28
|
+
$prefix = trim(Env::string('MCP_PATH_PREFIX', 'mcp'), '/');
|
|
29
|
+
$enableJson = Env::bool('MCP_JSON_RESPONSE', false);
|
|
29
30
|
|
|
30
31
|
// ── Build server and discover tools ───────────────────────────────────────────
|
|
31
32
|
$server = Server::make()
|
|
@@ -7,6 +7,7 @@ namespace Lib\Middleware;
|
|
|
7
7
|
use Lib\Auth\Auth;
|
|
8
8
|
use Lib\Auth\AuthConfig;
|
|
9
9
|
use PP\Request;
|
|
10
|
+
use Throwable;
|
|
10
11
|
|
|
11
12
|
final class AuthMiddleware
|
|
12
13
|
{
|
|
@@ -24,9 +25,18 @@ final class AuthMiddleware
|
|
|
24
25
|
// Check if the user is authenticated and refresh the token if necessary
|
|
25
26
|
if (AuthConfig::IS_TOKEN_AUTO_REFRESH) {
|
|
26
27
|
$auth = Auth::getInstance();
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
|
|
29
|
+
$jwt = $_COOKIE[Auth::$cookieName] ?? Request::getBearerToken();
|
|
30
|
+
|
|
31
|
+
if ($jwt) {
|
|
32
|
+
try {
|
|
33
|
+
$newJwt = $auth->refreshToken($jwt);
|
|
34
|
+
if (!isset($_COOKIE[Auth::$cookieName])) {
|
|
35
|
+
header('Authorization: Bearer ' . $newJwt);
|
|
36
|
+
header('X-Refreshed-Token: ' . $newJwt);
|
|
37
|
+
}
|
|
38
|
+
} catch (Throwable) {
|
|
39
|
+
}
|
|
30
40
|
}
|
|
31
41
|
}
|
|
32
42
|
|
|
@@ -89,29 +99,35 @@ final class AuthMiddleware
|
|
|
89
99
|
protected static function isAuthorized(): bool
|
|
90
100
|
{
|
|
91
101
|
$auth = Auth::getInstance();
|
|
92
|
-
|
|
93
|
-
|
|
102
|
+
$jwt = $_COOKIE[Auth::$cookieName] ?? Request::getBearerToken();
|
|
103
|
+
|
|
104
|
+
if (!$jwt) {
|
|
105
|
+
if (isset($_SESSION[Auth::PAYLOAD_SESSION_KEY])) {
|
|
106
|
+
unset($_SESSION[Auth::PAYLOAD_SESSION_KEY]);
|
|
107
|
+
}
|
|
94
108
|
return false;
|
|
95
109
|
}
|
|
96
110
|
|
|
97
|
-
$jwt = $_COOKIE[Auth::$cookieName];
|
|
98
|
-
|
|
99
111
|
if (AuthConfig::IS_TOKEN_AUTO_REFRESH) {
|
|
100
|
-
|
|
101
|
-
|
|
112
|
+
try {
|
|
113
|
+
$jwt = $auth->refreshToken($jwt);
|
|
114
|
+
|
|
115
|
+
if (!isset($_COOKIE[Auth::$cookieName]) && !headers_sent()) {
|
|
116
|
+
header('Authorization: Bearer ' . $jwt);
|
|
117
|
+
header('X-Refreshed-Token: ' . $jwt);
|
|
118
|
+
}
|
|
119
|
+
} catch (Throwable) {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
102
122
|
}
|
|
103
123
|
|
|
104
124
|
$verifyToken = $auth->verifyToken($jwt);
|
|
125
|
+
|
|
105
126
|
if ($verifyToken === false) {
|
|
106
127
|
return false;
|
|
107
128
|
}
|
|
108
129
|
|
|
109
|
-
|
|
110
|
-
if (isset($verifyToken->{Auth::PAYLOAD_NAME})) {
|
|
111
|
-
return true;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
return false;
|
|
130
|
+
return isset($verifyToken->{Auth::PAYLOAD_NAME});
|
|
115
131
|
}
|
|
116
132
|
|
|
117
133
|
protected static function hasRequiredRole(string $requestPathname): string
|
|
@@ -4,6 +4,8 @@ declare(strict_types=1);
|
|
|
4
4
|
|
|
5
5
|
namespace Lib\Middleware;
|
|
6
6
|
|
|
7
|
+
use PP\Env;
|
|
8
|
+
|
|
7
9
|
final class CorsMiddleware
|
|
8
10
|
{
|
|
9
11
|
public static function handle(?array $overrides = null): void
|
|
@@ -58,14 +60,14 @@ final class CorsMiddleware
|
|
|
58
60
|
|
|
59
61
|
private static function buildConfig(?array $overrides): array
|
|
60
62
|
{
|
|
61
|
-
$allowed = self::parseList(
|
|
63
|
+
$allowed = self::parseList(Env::string('CORS_ALLOWED_ORIGINS', ''));
|
|
62
64
|
$cfg = [
|
|
63
65
|
'allowedOrigins' => $allowed,
|
|
64
|
-
'allowCredentials' =>
|
|
65
|
-
'allowedMethods' =>
|
|
66
|
-
'allowedHeaders' => trim(
|
|
67
|
-
'exposeHeaders' => trim(
|
|
68
|
-
'maxAge' =>
|
|
66
|
+
'allowCredentials' => Env::bool('CORS_ALLOW_CREDENTIALS', false),
|
|
67
|
+
'allowedMethods' => Env::string('CORS_ALLOWED_METHODS', 'GET, POST, PUT, PATCH, DELETE, OPTIONS'),
|
|
68
|
+
'allowedHeaders' => trim(Env::string('CORS_ALLOWED_HEADERS', '')),
|
|
69
|
+
'exposeHeaders' => trim(Env::string('CORS_EXPOSE_HEADERS', '')),
|
|
70
|
+
'maxAge' => Env::int('CORS_MAX_AGE', 86400),
|
|
69
71
|
];
|
|
70
72
|
|
|
71
73
|
if (is_array($overrides)) {
|
|
@@ -15,12 +15,12 @@ class ConnectionManager implements MessageComponentInterface
|
|
|
15
15
|
|
|
16
16
|
public function __construct()
|
|
17
17
|
{
|
|
18
|
-
$this->clients = new SplObjectStorage;
|
|
18
|
+
$this->clients = new SplObjectStorage();
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
public function onOpen(ConnectionInterface $conn): void
|
|
22
22
|
{
|
|
23
|
-
$this->clients->
|
|
23
|
+
$this->clients->offsetSet($conn, true);
|
|
24
24
|
echo "New connection! ({$conn->resourceId})";
|
|
25
25
|
}
|
|
26
26
|
|
|
@@ -35,7 +35,7 @@ class ConnectionManager implements MessageComponentInterface
|
|
|
35
35
|
|
|
36
36
|
public function onClose(ConnectionInterface $conn): void
|
|
37
37
|
{
|
|
38
|
-
$this->clients->
|
|
38
|
+
$this->clients->offsetUnset($conn);
|
|
39
39
|
echo "Connection {$conn->resourceId} has disconnected";
|
|
40
40
|
}
|
|
41
41
|
|
|
@@ -15,14 +15,15 @@ use Ratchet\WebSocket\WsServer;
|
|
|
15
15
|
use Lib\Websocket\ConnectionManager;
|
|
16
16
|
use React\EventLoop\LoopInterface;
|
|
17
17
|
use Throwable;
|
|
18
|
+
use PP\Env;
|
|
18
19
|
|
|
19
20
|
// ── Load .env (optional) and timezone ─────────────────────────────────────────
|
|
20
21
|
if (file_exists(DOCUMENT_PATH . '/.env')) {
|
|
21
22
|
Dotenv::createImmutable(DOCUMENT_PATH)->safeLoad();
|
|
22
23
|
}
|
|
23
|
-
date_default_timezone_set(
|
|
24
|
+
date_default_timezone_set(Env::string('APP_TIMEZONE', 'UTC'));
|
|
24
25
|
|
|
25
|
-
// ── Tiny argv parser: allows --host=0.0.0.0 --port=
|
|
26
|
+
// ── Tiny argv parser: allows --host=0.0.0.0 --port=9001 ──────────────────────
|
|
26
27
|
$cli = [];
|
|
27
28
|
foreach ($argv ?? [] as $arg) {
|
|
28
29
|
if (preg_match('/^--([^=]+)=(.*)$/', $arg, $m)) {
|
|
@@ -31,11 +32,11 @@ foreach ($argv ?? [] as $arg) {
|
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
// ── Resolve settings (env → cli defaults) ────────────────────────────────────
|
|
34
|
-
$appName =
|
|
35
|
-
$appVer =
|
|
36
|
-
$host = $cli['host'] ?? (
|
|
37
|
-
$port = (int)($cli['port'] ?? (
|
|
38
|
-
$verbose = filter_var($cli['verbose'] ?? (
|
|
35
|
+
$appName = Env::string('WS_NAME', 'prisma-php-ws');
|
|
36
|
+
$appVer = Env::string('WS_VERSION', '0.0.1');
|
|
37
|
+
$host = $cli['host'] ?? Env::string('WS_HOST', '127.0.0.1');
|
|
38
|
+
$port = (int)($cli['port'] ?? Env::int('WS_PORT', 9001));
|
|
39
|
+
$verbose = filter_var($cli['verbose'] ?? Env::bool('WS_VERBOSE', true), FILTER_VALIDATE_BOOLEAN);
|
|
39
40
|
|
|
40
41
|
// ── Console helpers ──────────────────────────────────────────────────────────
|
|
41
42
|
$color = static fn(string $t, string $c) => "\033[{$c}m{$t}\033[0m";
|
package/dist/src/app/index.php
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<ol class="list-inside list-decimal text-sm/6 text-center sm:text-left">
|
|
9
9
|
<li class="mb-2 tracking-[-.01em]">
|
|
10
10
|
Get started by editing
|
|
11
|
-
<code class="bg-text-foreground/[.05] dark:bg-white/
|
|
11
|
+
<code class="bg-text-foreground/[.05] dark:bg-white/6 px-1 py-0.5 rounded font-semibold">
|
|
12
12
|
src/app/index.php
|
|
13
13
|
</code>
|
|
14
14
|
.
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
</div>
|
|
47
47
|
</main>
|
|
48
48
|
|
|
49
|
-
<footer class="row-start-3 flex gap-
|
|
49
|
+
<footer class="row-start-3 flex gap-6 flex-wrap items-center justify-center">
|
|
50
50
|
<a
|
|
51
51
|
class="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
|
52
52
|
href="https://prismaphp.tsnc.tech/docs?doc=learning-path"
|
package/dist/src/app/layout.php
CHANGED
|
@@ -15,7 +15,7 @@ MainLayout::$description = !empty(MainLayout::$description) ? MainLayout::$descr
|
|
|
15
15
|
<!-- Dynamic Header Scripts -->
|
|
16
16
|
</head>
|
|
17
17
|
|
|
18
|
-
<body pp-spa="true">
|
|
18
|
+
<body pp-spa="true" style="opacity:0;pointer-events:none;user-select:none;transition:opacity .18s ease-out;">
|
|
19
19
|
<?= MainLayout::$children; ?>
|
|
20
20
|
<!-- Dynamic Footer Scripts -->
|
|
21
21
|
</body>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
<div class="flex flex-col min-h-
|
|
1
|
+
<div class="flex flex-col min-h-screen items-center justify-center space-y-4 text-center">
|
|
2
2
|
<div class="space-y-2">
|
|
3
3
|
<h1 class="text-4xl font-bold tracking-tighter sm:text-5xl md:text-6xl">404 Not Found</h1>
|
|
4
|
-
<p class="max-w-
|
|
4
|
+
<p class="max-w-150 text-gray-500 md:text-xl/relaxed lg:text-base/relaxed xl:text-xl/relaxed dark:text-gray-400">
|
|
5
5
|
Sorry, we couldn't find the page you're looking for.
|
|
6
6
|
</p>
|
|
7
7
|
</div>
|
package/dist/tsconfig.json
CHANGED
|
@@ -106,6 +106,6 @@
|
|
|
106
106
|
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
|
107
107
|
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
|
108
108
|
},
|
|
109
|
-
|
|
109
|
+
"include": ["**/*.ts", ".pp/**/*.d.ts"],
|
|
110
110
|
"exclude": ["node_modules", "vendor"]
|
|
111
111
|
}
|
package/dist/vite.config.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { defineConfig, Plugin } from "vite";
|
|
|
2
2
|
import path from "path";
|
|
3
3
|
import fg from "fast-glob";
|
|
4
4
|
import { writeFileSync } from "fs";
|
|
5
|
+
import { generateGlobalTypes } from "./settings/vite-plugins/generate-global-types.js";
|
|
5
6
|
|
|
6
7
|
const entries = Object.fromEntries(
|
|
7
8
|
fg.sync("ts/**/*.ts", { ignore: ["**/*.test.ts"] }).map((f) => {
|
|
@@ -10,6 +11,13 @@ const entries = Object.fromEntries(
|
|
|
10
11
|
})
|
|
11
12
|
);
|
|
12
13
|
|
|
14
|
+
const VITE_WATCH_EXCLUDE = [
|
|
15
|
+
"public/js/**",
|
|
16
|
+
"node_modules/**",
|
|
17
|
+
"vendor/**",
|
|
18
|
+
".pp/**",
|
|
19
|
+
];
|
|
20
|
+
|
|
13
21
|
function browserSyncNotify(): Plugin {
|
|
14
22
|
const flagFile = path.resolve(__dirname, ".pp", ".vite-build-complete");
|
|
15
23
|
|
|
@@ -28,9 +36,10 @@ export default defineConfig(({ command, mode }) => ({
|
|
|
28
36
|
emptyOutDir: false,
|
|
29
37
|
minify: "esbuild",
|
|
30
38
|
sourcemap: false,
|
|
39
|
+
chunkSizeWarningLimit: 1000,
|
|
31
40
|
watch:
|
|
32
41
|
command === "build" && mode === "development"
|
|
33
|
-
? { exclude:
|
|
42
|
+
? { exclude: VITE_WATCH_EXCLUDE }
|
|
34
43
|
: undefined,
|
|
35
44
|
rollupOptions: {
|
|
36
45
|
input: entries,
|
|
@@ -39,11 +48,24 @@ export default defineConfig(({ command, mode }) => ({
|
|
|
39
48
|
entryFileNames: "[name].js",
|
|
40
49
|
chunkFileNames: "chunks/[name]-[hash].js",
|
|
41
50
|
assetFileNames: "assets/[name]-[hash][extname]",
|
|
51
|
+
manualChunks(id) {
|
|
52
|
+
if (id.includes("node_modules")) {
|
|
53
|
+
return id
|
|
54
|
+
.toString()
|
|
55
|
+
.split("node_modules/")[1]
|
|
56
|
+
.split("/")[0]
|
|
57
|
+
.toString();
|
|
58
|
+
}
|
|
59
|
+
},
|
|
42
60
|
},
|
|
43
61
|
},
|
|
44
62
|
},
|
|
45
|
-
plugins:
|
|
46
|
-
|
|
63
|
+
plugins: [
|
|
64
|
+
generateGlobalTypes(),
|
|
65
|
+
...(command === "build" && mode === "development"
|
|
66
|
+
? [browserSyncNotify()]
|
|
67
|
+
: []),
|
|
68
|
+
],
|
|
47
69
|
esbuild: { legalComments: "none" },
|
|
48
70
|
define: { "process.env.NODE_ENV": '"production"' },
|
|
49
71
|
}));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-prisma-php-app",
|
|
3
|
-
"version": "4.4.4
|
|
3
|
+
"version": "4.4.4",
|
|
4
4
|
"description": "Prisma-PHP: A Revolutionary Library Bridging PHP with Prisma ORM",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -32,15 +32,15 @@
|
|
|
32
32
|
"author": "Jefferson Abraham Omier <thesteelninjacode@gmail.com>",
|
|
33
33
|
"license": "MIT",
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"chalk": "^5.
|
|
35
|
+
"chalk": "^5.6.2",
|
|
36
36
|
"crypto-js": "^4.2.0",
|
|
37
37
|
"prompts": "^2.4.2"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@types/crypto-js": "^4.2.2",
|
|
41
|
-
"@types/node": "^
|
|
41
|
+
"@types/node": "^25.0.3",
|
|
42
42
|
"@types/prompts": "^2.4.9",
|
|
43
43
|
"ts-node": "^10.9.2",
|
|
44
|
-
"typescript": "^5.
|
|
44
|
+
"typescript": "^5.9.3"
|
|
45
45
|
}
|
|
46
46
|
}
|