create-lumina-project 1.2.0 → 2.0.0
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/package.json +3 -3
- package/template/.env.example +2 -1
- package/template/nodemon.json +1 -1
- package/template/package.json +26 -15
- package/template/resources/css/app.css +491 -0
- package/template/resources/js/Pages/Errors/404.tsx +32 -0
- package/template/resources/js/Pages/Maintenance.tsx +34 -0
- package/template/resources/js/Pages/Status.tsx +103 -0
- package/template/resources/js/Pages/Welcome.tsx +82 -0
- package/template/resources/js/app.tsx +43 -0
- package/template/resources/js/tsconfig.json +20 -0
- package/template/resources/js/vite-env.d.ts +1 -0
- package/template/resources/views/app.html +14 -0
- package/template/src/controllers/UserController.ts +5 -1
- package/template/src/controllers/WebController.ts +94 -0
- package/template/src/database/migrations/20260420050855-create_refresh_tokens.js +5 -0
- package/template/src/exceptions/Handler.ts +4 -4
- package/template/src/middlewares/ApiAuth.ts +67 -0
- package/template/src/middlewares/Csrf.ts +5 -4
- package/template/src/middlewares/InertiaMiddleware.ts +95 -0
- package/template/src/middlewares/Maintenance.ts +5 -5
- package/template/src/middlewares/RoleGuard.ts +20 -0
- package/template/src/middlewares/Validator.ts +17 -1
- package/template/src/middlewares/WebAuth.ts +122 -0
- package/template/src/models/BaseModel.ts +24 -0
- package/template/src/models/RefreshToken.ts +6 -5
- package/template/src/models/User.ts +3 -5
- package/template/src/routes/api.ts +16 -8
- package/template/src/routes/web.ts +10 -43
- package/template/src/server.ts +115 -0
- package/template/src/types/express/index.d.ts +8 -1
- package/template/src/types/express-inertia.d.ts +28 -0
- package/template/vite.config.ts +20 -0
- package/template/views/404.html +0 -233
- package/template/views/maintenance.html +0 -464
- package/template/views/status.html +0 -545
- package/template/views/welcome.html +0 -495
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { Request, Response, NextFunction } from 'express';
|
|
2
|
+
import jwt from 'jsonwebtoken';
|
|
3
|
+
import env from '../config/env.js';
|
|
4
|
+
import AuthService from '../services/AuthService.js';
|
|
5
|
+
import Logger from '../utils/Logger.js';
|
|
6
|
+
import { UserPayload } from '../types/express/index.js';
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class WebAuth {
|
|
10
|
+
/**
|
|
11
|
+
* Middleware to protect web routes and redirect to login if not authenticated.
|
|
12
|
+
*/
|
|
13
|
+
public async handle(req: Request, res: Response, next: NextFunction) {
|
|
14
|
+
const accessToken = req.cookies?.access_token;
|
|
15
|
+
const refreshToken = req.cookies?.refresh_token;
|
|
16
|
+
|
|
17
|
+
if (!accessToken) {
|
|
18
|
+
// If no access token, try to refresh
|
|
19
|
+
if (refreshToken) {
|
|
20
|
+
return this.silentRefresh(req, res, next, refreshToken);
|
|
21
|
+
}
|
|
22
|
+
return res.redirect('/login');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
// Verify access token
|
|
27
|
+
const decoded = jwt.verify(accessToken, env.JWT_SECRET) as UserPayload;
|
|
28
|
+
req.user = decoded;
|
|
29
|
+
next();
|
|
30
|
+
} catch (error) {
|
|
31
|
+
// Access token invalid/expired, try refresh
|
|
32
|
+
if (refreshToken) {
|
|
33
|
+
return this.silentRefresh(req, res, next, refreshToken);
|
|
34
|
+
}
|
|
35
|
+
res.clearCookie('access_token');
|
|
36
|
+
return res.redirect('/login');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Middleware to detect the user but allow the request to continue.
|
|
42
|
+
* Useful for public pages that need to know if a user is logged in.
|
|
43
|
+
*/
|
|
44
|
+
public async detect(req: Request, res: Response, next: NextFunction) {
|
|
45
|
+
const accessToken = req.cookies?.access_token;
|
|
46
|
+
const refreshToken = req.cookies?.refresh_token;
|
|
47
|
+
|
|
48
|
+
if (!accessToken) {
|
|
49
|
+
if (refreshToken) {
|
|
50
|
+
try {
|
|
51
|
+
const { accessToken: newToken } = await AuthService.refresh(refreshToken);
|
|
52
|
+
res.cookie('access_token', newToken, {
|
|
53
|
+
httpOnly: true,
|
|
54
|
+
secure: process.env.NODE_ENV === 'production',
|
|
55
|
+
sameSite: 'strict',
|
|
56
|
+
maxAge: 15 * 60 * 1000,
|
|
57
|
+
});
|
|
58
|
+
const decoded = jwt.verify(newToken, env.JWT_SECRET) as UserPayload;
|
|
59
|
+
req.user = decoded;
|
|
60
|
+
} catch (error) {
|
|
61
|
+
// Silent failure
|
|
62
|
+
Logger.debug('Silent refresh failed in detect middleware');
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return next();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
const decoded = jwt.verify(accessToken, env.JWT_SECRET) as UserPayload;
|
|
70
|
+
req.user = decoded;
|
|
71
|
+
next();
|
|
72
|
+
} catch (error) {
|
|
73
|
+
// Access token invalid/expired, try silent refresh
|
|
74
|
+
if (refreshToken) {
|
|
75
|
+
try {
|
|
76
|
+
const { accessToken: newToken } = await AuthService.refresh(refreshToken);
|
|
77
|
+
res.cookie('access_token', newToken, {
|
|
78
|
+
httpOnly: true,
|
|
79
|
+
secure: process.env.NODE_ENV === 'production',
|
|
80
|
+
sameSite: 'strict',
|
|
81
|
+
maxAge: 15 * 60 * 1000,
|
|
82
|
+
});
|
|
83
|
+
const decoded = jwt.verify(newToken, env.JWT_SECRET) as UserPayload;
|
|
84
|
+
req.user = decoded;
|
|
85
|
+
} catch (err) {
|
|
86
|
+
Logger.debug('Silent refresh failed in detect middleware after verify error');
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
next();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Attempt to refresh the access token silently.
|
|
95
|
+
*/
|
|
96
|
+
private async silentRefresh(req: Request, res: Response, next: NextFunction, token: string) {
|
|
97
|
+
try {
|
|
98
|
+
const { accessToken } = await AuthService.refresh(token);
|
|
99
|
+
|
|
100
|
+
// Set new access token cookie
|
|
101
|
+
res.cookie('access_token', accessToken, {
|
|
102
|
+
httpOnly: true,
|
|
103
|
+
secure: process.env.NODE_ENV === 'production',
|
|
104
|
+
sameSite: 'strict',
|
|
105
|
+
maxAge: 15 * 60 * 1000,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Verify the new token to get user data
|
|
109
|
+
const decoded = jwt.verify(accessToken, env.JWT_SECRET) as UserPayload;
|
|
110
|
+
req.user = decoded;
|
|
111
|
+
|
|
112
|
+
next();
|
|
113
|
+
} catch (error) {
|
|
114
|
+
res.clearCookie('access_token');
|
|
115
|
+
res.clearCookie('refresh_token');
|
|
116
|
+
return res.redirect('/login');
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
export default new WebAuth();
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Model, ModelAttributes, InitOptions } from 'sequelize';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* BaseModel provides shared configuration for all models.
|
|
5
|
+
* It automatically enables Soft Deletes (paranoid), Timestamps, and Underscored naming.
|
|
6
|
+
*/
|
|
7
|
+
export default class BaseModel<TAttributes extends {} = any, TCreationAttributes extends {} = any>
|
|
8
|
+
extends Model<TAttributes, TCreationAttributes> {
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Helper to initialize the model with default options.
|
|
12
|
+
*/
|
|
13
|
+
public static init<MS extends Model, M extends typeof Model>(
|
|
14
|
+
attributes: ModelAttributes<MS, any>,
|
|
15
|
+
options: InitOptions<MS>
|
|
16
|
+
): M {
|
|
17
|
+
return super.init(attributes, {
|
|
18
|
+
paranoid: true,
|
|
19
|
+
timestamps: true,
|
|
20
|
+
underscored: true,
|
|
21
|
+
...options,
|
|
22
|
+
}) as unknown as M;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { DataTypes, Sequelize, Optional } from 'sequelize';
|
|
2
|
+
import BaseModel from './BaseModel.js';
|
|
2
3
|
|
|
3
4
|
interface RefreshTokenAttributes {
|
|
4
5
|
id: number;
|
|
@@ -8,11 +9,12 @@ interface RefreshTokenAttributes {
|
|
|
8
9
|
revoked: boolean;
|
|
9
10
|
created_at?: Date;
|
|
10
11
|
updated_at?: Date;
|
|
12
|
+
deleted_at?: Date | null;
|
|
11
13
|
}
|
|
12
14
|
|
|
13
|
-
export interface RefreshTokenCreationAttributes extends Optional<RefreshTokenAttributes, 'id' | 'revoked' | 'created_at' | 'updated_at'> {}
|
|
15
|
+
export interface RefreshTokenCreationAttributes extends Optional<RefreshTokenAttributes, 'id' | 'revoked' | 'created_at' | 'updated_at' | 'deleted_at'> {}
|
|
14
16
|
|
|
15
|
-
class RefreshToken extends
|
|
17
|
+
class RefreshToken extends BaseModel<RefreshTokenAttributes, RefreshTokenCreationAttributes> implements RefreshTokenAttributes {
|
|
16
18
|
declare id: number;
|
|
17
19
|
declare user_id: number;
|
|
18
20
|
declare token: string;
|
|
@@ -20,6 +22,7 @@ class RefreshToken extends Model<RefreshTokenAttributes, RefreshTokenCreationAtt
|
|
|
20
22
|
declare revoked: boolean;
|
|
21
23
|
declare created_at: Date;
|
|
22
24
|
declare updated_at: Date;
|
|
25
|
+
declare deleted_at: Date | null;
|
|
23
26
|
|
|
24
27
|
static initModel(sequelize: Sequelize) {
|
|
25
28
|
RefreshToken.init(
|
|
@@ -52,8 +55,6 @@ class RefreshToken extends Model<RefreshTokenAttributes, RefreshTokenCreationAtt
|
|
|
52
55
|
sequelize,
|
|
53
56
|
modelName: 'RefreshToken',
|
|
54
57
|
tableName: 'refresh_tokens',
|
|
55
|
-
timestamps: true,
|
|
56
|
-
underscored: true,
|
|
57
58
|
}
|
|
58
59
|
);
|
|
59
60
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { DataTypes, Sequelize, Optional } from 'sequelize';
|
|
2
|
+
import BaseModel from './BaseModel.js';
|
|
2
3
|
import Hash from '../utils/Hash.js';
|
|
3
4
|
|
|
4
5
|
interface UserAttributes {
|
|
@@ -16,7 +17,7 @@ interface UserAttributes {
|
|
|
16
17
|
|
|
17
18
|
export interface UserCreationAttributes extends Optional<UserAttributes, 'id' | 'created_at' | 'updated_at' | 'deleted_at' | 'role' | 'avatar'> {}
|
|
18
19
|
|
|
19
|
-
class User extends
|
|
20
|
+
class User extends BaseModel<UserAttributes, UserCreationAttributes> implements UserAttributes {
|
|
20
21
|
declare id: number;
|
|
21
22
|
declare firstname: string;
|
|
22
23
|
declare lastname: string;
|
|
@@ -68,9 +69,6 @@ class User extends Model<UserAttributes, UserCreationAttributes> implements User
|
|
|
68
69
|
sequelize,
|
|
69
70
|
modelName: 'User',
|
|
70
71
|
tableName: 'users',
|
|
71
|
-
paranoid: true,
|
|
72
|
-
timestamps: true,
|
|
73
|
-
underscored: true,
|
|
74
72
|
hooks: {
|
|
75
73
|
beforeCreate: async (user: User) => {
|
|
76
74
|
if (user.password) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Router } from 'express';
|
|
2
2
|
import UserController from '../controllers/UserController.js';
|
|
3
|
-
import
|
|
3
|
+
import ApiAuth from '../middlewares/ApiAuth.js';
|
|
4
|
+
import RoleGuard from '../middlewares/RoleGuard.js';
|
|
4
5
|
import Validator from '../middlewares/Validator.js';
|
|
5
6
|
import UserRequest from '../requests/UserRequest.js';
|
|
6
7
|
import AuthController from '../controllers/AuthController.js';
|
|
@@ -19,17 +20,24 @@ class ApiRoutes {
|
|
|
19
20
|
* Define all API routes here.
|
|
20
21
|
*/
|
|
21
22
|
protected initializeRoutes(): void {
|
|
22
|
-
//
|
|
23
|
+
// --- Public Routes ---
|
|
23
24
|
this.router.post('/login', Limiter.auth, AuthController.login);
|
|
24
25
|
this.router.post('/refresh', AuthController.refresh);
|
|
25
|
-
this.router.post('/logout', Authentication.handle, AuthController.logout);
|
|
26
26
|
|
|
27
|
-
// Protected
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
// --- Protected Routes Group ---
|
|
28
|
+
const protectedRouter = Router();
|
|
29
|
+
protectedRouter.use(ApiAuth.handle);
|
|
30
|
+
|
|
31
|
+
protectedRouter.post('/logout', AuthController.logout);
|
|
32
|
+
protectedRouter.get('/me', AuthController.me);
|
|
33
|
+
protectedRouter.get('/users', RoleGuard.allow('admin'), UserController.index);
|
|
34
|
+
protectedRouter.post('/users', RoleGuard.allow('admin'), Validator.validate(UserRequest.store), UserController.store);
|
|
35
|
+
protectedRouter.post('/users/avatar', StorageService.uploader.single('avatar'), UserController.uploadAvatar);
|
|
36
|
+
|
|
37
|
+
// Mount the group
|
|
38
|
+
this.router.use(protectedRouter);
|
|
32
39
|
}
|
|
33
40
|
}
|
|
34
41
|
|
|
42
|
+
|
|
35
43
|
export default new ApiRoutes().router;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { Router
|
|
2
|
-
import
|
|
3
|
-
import
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import WebController from '../controllers/WebController.js';
|
|
3
|
+
import Csrf from '../middlewares/Csrf.js';
|
|
4
4
|
|
|
5
5
|
class WebRoutes {
|
|
6
6
|
public router: Router;
|
|
@@ -11,48 +11,15 @@ class WebRoutes {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
protected initializeRoutes(): void {
|
|
14
|
-
//
|
|
15
|
-
this.router.
|
|
16
|
-
const viewPath = path.join(process.cwd(), 'views', 'welcome.html');
|
|
17
|
-
res.sendFile(viewPath);
|
|
18
|
-
});
|
|
14
|
+
// Apply CSRF protection to all web routes
|
|
15
|
+
this.router.use(Csrf.handle);
|
|
19
16
|
|
|
20
|
-
//
|
|
21
|
-
this.router.get('/
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
});
|
|
17
|
+
// --- Public Routes ---
|
|
18
|
+
this.router.get('/', WebController.index);
|
|
19
|
+
this.router.get('/status', WebController.status);
|
|
20
|
+
this.router.get('/health', WebController.health);
|
|
25
21
|
|
|
26
|
-
// Status JSON API (used by the status view)
|
|
27
|
-
this.router.get('/status/json', (req: Request, res: Response) => {
|
|
28
|
-
res.json({
|
|
29
|
-
status: 'UP',
|
|
30
|
-
environment: process.env.NODE_ENV,
|
|
31
|
-
uptime: process.uptime(),
|
|
32
|
-
memoryMB: (process.memoryUsage().rss / 1024 / 1024).toFixed(1),
|
|
33
|
-
});
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
// Health Check (for container/orchestration probes)
|
|
37
|
-
this.router.get('/health', async (req: Request, res: Response) => {
|
|
38
|
-
try {
|
|
39
|
-
await db.sequelize.authenticate();
|
|
40
|
-
res.json({
|
|
41
|
-
status: 'healthy',
|
|
42
|
-
uptime: process.uptime(),
|
|
43
|
-
database: 'connected',
|
|
44
|
-
timestamp: new Date().toISOString(),
|
|
45
|
-
});
|
|
46
|
-
} catch {
|
|
47
|
-
res.status(503).json({
|
|
48
|
-
status: 'unhealthy',
|
|
49
|
-
uptime: process.uptime(),
|
|
50
|
-
database: 'disconnected',
|
|
51
|
-
timestamp: new Date().toISOString(),
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
22
|
}
|
|
56
23
|
}
|
|
57
24
|
|
|
58
|
-
export default new WebRoutes().router;
|
|
25
|
+
export default new WebRoutes().router;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import express, { Application } from 'express';
|
|
2
|
+
import cors from 'cors';
|
|
3
|
+
import helmet from 'helmet';
|
|
4
|
+
import compression from 'compression';
|
|
5
|
+
import cookieParser from 'cookie-parser';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import db from './models/index.js';
|
|
8
|
+
import RouteService from './services/RouteService.js';
|
|
9
|
+
import ExceptionHandler from './exceptions/Handler.js';
|
|
10
|
+
import Logger from './utils/Logger.js';
|
|
11
|
+
import Limiter from './middlewares/Limiter.js';
|
|
12
|
+
import Maintenance from './middlewares/Maintenance.js';
|
|
13
|
+
import RequestLogger from './middlewares/RequestLogger.js';
|
|
14
|
+
import env from './config/env.js';
|
|
15
|
+
import InertiaMiddleware from './middlewares/InertiaMiddleware.js';
|
|
16
|
+
import WebAuth from './middlewares/WebAuth.js';
|
|
17
|
+
|
|
18
|
+
const app: Application = express();
|
|
19
|
+
const PORT = env.APP_PORT;
|
|
20
|
+
|
|
21
|
+
// ==========================
|
|
22
|
+
// Global Middleware
|
|
23
|
+
// ==========================
|
|
24
|
+
app.use(Maintenance.handle);
|
|
25
|
+
app.use(helmet({
|
|
26
|
+
crossOriginResourcePolicy: false,
|
|
27
|
+
contentSecurityPolicy: env.NODE_ENV === 'production' ? undefined : false,
|
|
28
|
+
}));
|
|
29
|
+
app.use(cors({
|
|
30
|
+
origin: env.CORS_ORIGIN,
|
|
31
|
+
credentials: true,
|
|
32
|
+
}));
|
|
33
|
+
app.use(compression());
|
|
34
|
+
app.use(cookieParser());
|
|
35
|
+
app.use(express.json({ limit: '10kb' }));
|
|
36
|
+
app.use(express.urlencoded({ extended: true, limit: '10kb' }));
|
|
37
|
+
|
|
38
|
+
// User detection and Inertia
|
|
39
|
+
app.use(WebAuth.detect.bind(WebAuth));
|
|
40
|
+
app.use(InertiaMiddleware.handle);
|
|
41
|
+
|
|
42
|
+
app.use(express.static(path.join(process.cwd(), 'public')));
|
|
43
|
+
app.use(Limiter.global);
|
|
44
|
+
app.use(RequestLogger.handle);
|
|
45
|
+
|
|
46
|
+
// ==========================
|
|
47
|
+
// Register Routes
|
|
48
|
+
// ==========================
|
|
49
|
+
// This loads both your API and Web routes
|
|
50
|
+
RouteService.boot(app);
|
|
51
|
+
|
|
52
|
+
// ==========================
|
|
53
|
+
// Error Handling
|
|
54
|
+
// ==========================
|
|
55
|
+
|
|
56
|
+
// 404 Not Found Handler
|
|
57
|
+
app.use(ExceptionHandler.notFound);
|
|
58
|
+
|
|
59
|
+
// Global Error Handler
|
|
60
|
+
app.use(ExceptionHandler.handle);
|
|
61
|
+
|
|
62
|
+
// ==========================
|
|
63
|
+
// Start Server
|
|
64
|
+
// ==========================
|
|
65
|
+
let server: ReturnType<typeof app.listen>;
|
|
66
|
+
|
|
67
|
+
const start = async () => {
|
|
68
|
+
try {
|
|
69
|
+
// Test Database Connection
|
|
70
|
+
await db.connect();
|
|
71
|
+
|
|
72
|
+
// Start Listening
|
|
73
|
+
server = app.listen(PORT, () => {
|
|
74
|
+
Logger.info(`Server running on http://localhost:${PORT}`);
|
|
75
|
+
});
|
|
76
|
+
} catch (error) {
|
|
77
|
+
Logger.error('Unable to connect to the database:', error);
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// ==========================
|
|
83
|
+
// Graceful Shutdown
|
|
84
|
+
// ==========================
|
|
85
|
+
const shutdown = async (signal: string) => {
|
|
86
|
+
Logger.info(`${signal} received. Shutting down gracefully...`);
|
|
87
|
+
|
|
88
|
+
if (server) {
|
|
89
|
+
server.close(async () => {
|
|
90
|
+
Logger.info('HTTP server closed.');
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
await db.sequelize.close();
|
|
94
|
+
Logger.info('Database connection closed.');
|
|
95
|
+
} catch (error) {
|
|
96
|
+
Logger.error('Error closing database connection:', error);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
process.exit(0);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// Force shutdown after 10 seconds
|
|
103
|
+
setTimeout(() => {
|
|
104
|
+
Logger.error('Forced shutdown after timeout.');
|
|
105
|
+
process.exit(1);
|
|
106
|
+
}, 10000);
|
|
107
|
+
} else {
|
|
108
|
+
process.exit(0);
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
|
113
|
+
process.on('SIGINT', () => shutdown('SIGINT'));
|
|
114
|
+
|
|
115
|
+
start();
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import { JwtPayload } from 'jsonwebtoken';
|
|
2
2
|
|
|
3
|
+
|
|
4
|
+
export interface UserPayload extends JwtPayload {
|
|
5
|
+
id: number;
|
|
6
|
+
email: string;
|
|
7
|
+
role: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
3
10
|
declare global {
|
|
4
11
|
namespace Express {
|
|
5
12
|
interface Request {
|
|
6
|
-
user?:
|
|
13
|
+
user?: UserPayload;
|
|
7
14
|
}
|
|
8
15
|
}
|
|
9
16
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
declare module 'inertia-node' {
|
|
2
|
+
import { Request, Response, NextFunction } from 'express';
|
|
3
|
+
|
|
4
|
+
export interface Inertia {
|
|
5
|
+
render(page: { component: string; props?: Record<string, any> }): void;
|
|
6
|
+
shareProps(props: Record<string, any>): void;
|
|
7
|
+
setViewData(data: Record<string, any>): void;
|
|
8
|
+
redirect(url?: string): void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function inertia(
|
|
12
|
+
html: (page: string) => string,
|
|
13
|
+
version?: string | (() => string)
|
|
14
|
+
): (req: Request, res: Response, next: NextFunction) => void;
|
|
15
|
+
|
|
16
|
+
export default inertia;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
declare namespace Express {
|
|
20
|
+
export interface Request {
|
|
21
|
+
Inertia: import('inertia-node').Inertia;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface Response {
|
|
25
|
+
inertia(component: string, props?: Record<string, any>): void;
|
|
26
|
+
flash(type: 'success' | 'error' | 'info' | 'warning' | string, message: string): void;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { defineConfig } from 'vite';
|
|
2
|
+
import react from '@vitejs/plugin-react';
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
plugins: [react()],
|
|
6
|
+
build: {
|
|
7
|
+
outDir: 'public/build',
|
|
8
|
+
manifest: true,
|
|
9
|
+
rollupOptions: {
|
|
10
|
+
input: 'resources/js/app.tsx',
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
server: {
|
|
14
|
+
strictPort: true,
|
|
15
|
+
port: 5173,
|
|
16
|
+
hmr: {
|
|
17
|
+
host: 'localhost',
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
});
|