frontend-hamroun 1.2.24 → 1.2.26
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/README.md +3 -0
- package/dist/index.d.ts +6 -1
- package/package.json +1 -1
- package/templates/fullstack-app/api/hello.js +11 -0
- package/templates/fullstack-app/index.html +13 -0
- package/templates/fullstack-app/package-lock.json +3130 -0
- package/templates/fullstack-app/package.json +8 -19
- package/templates/fullstack-app/server.js +210 -0
- package/templates/fullstack-app/server.ts +70 -31
- package/templates/fullstack-app/src/client.js +20 -0
- package/templates/fullstack-app/src/main.tsx +9 -0
- package/templates/fullstack-app/src/pages/index.tsx +40 -36
- package/templates/fullstack-app/vite.config.js +40 -0
- package/templates/ssr-template/package-lock.json +95 -1161
- package/templates/ssr-template/package.json +6 -4
- package/templates/ssr-template/readme.md +112 -37
- package/templates/ssr-template/server.js +364 -549
- package/templates/ssr-template/server.ts +166 -14
- package/templates/ssr-template/tsconfig.json +2 -3
@@ -2,6 +2,14 @@ import express from 'express';
|
|
2
2
|
import { fileURLToPath } from 'url';
|
3
3
|
import { dirname, join } from 'path';
|
4
4
|
import { renderToString } from 'frontend-hamroun';
|
5
|
+
import { Database } from 'frontend-hamroun';
|
6
|
+
import { AuthService } from 'frontend-hamroun';
|
7
|
+
import { requestLogger, errorHandler, notFoundHandler, rateLimit } from 'frontend-hamroun';
|
8
|
+
import dotenv from 'dotenv';
|
9
|
+
import fetch from 'node-fetch';
|
10
|
+
|
11
|
+
// Load environment variables
|
12
|
+
dotenv.config();
|
5
13
|
|
6
14
|
// Get directory name in ESM
|
7
15
|
const __filename = fileURLToPath(import.meta.url);
|
@@ -11,6 +19,67 @@ const __dirname = dirname(__filename);
|
|
11
19
|
const app = express();
|
12
20
|
const port = process.env.PORT ? parseInt(process.env.PORT) : 3000;
|
13
21
|
|
22
|
+
// Add middleware
|
23
|
+
app.use(express.json());
|
24
|
+
app.use(express.urlencoded({ extended: true }));
|
25
|
+
app.use(requestLogger);
|
26
|
+
|
27
|
+
// Rate limiting for API routes
|
28
|
+
app.use('/api', rateLimit({
|
29
|
+
windowMs: 15 * 60 * 1000, // 15 minutes
|
30
|
+
max: 100 // limit each IP to 100 requests per windowMs
|
31
|
+
}));
|
32
|
+
|
33
|
+
// Configure database if connection string is provided
|
34
|
+
let db = null;
|
35
|
+
if (process.env.DATABASE_URL) {
|
36
|
+
db = new Database({
|
37
|
+
url: process.env.DATABASE_URL,
|
38
|
+
type: (process.env.DATABASE_TYPE || 'mongodb') as 'mongodb' | 'mysql' | 'postgres'
|
39
|
+
});
|
40
|
+
|
41
|
+
// Connect to database
|
42
|
+
try {
|
43
|
+
await db.connect();
|
44
|
+
console.log('Database connected successfully');
|
45
|
+
} catch (error) {
|
46
|
+
console.error('Database connection failed:', error);
|
47
|
+
}
|
48
|
+
}
|
49
|
+
|
50
|
+
// Configure auth if secret is provided
|
51
|
+
let auth = null;
|
52
|
+
if (process.env.JWT_SECRET) {
|
53
|
+
auth = new AuthService({
|
54
|
+
secret: process.env.JWT_SECRET,
|
55
|
+
expiresIn: process.env.JWT_EXPIRES_IN || '24h'
|
56
|
+
});
|
57
|
+
|
58
|
+
// Add auth middleware
|
59
|
+
app.use(auth.initialize());
|
60
|
+
|
61
|
+
// Example protected route
|
62
|
+
app.get('/api/protected', auth.requireAuth(), (req, res) => {
|
63
|
+
res.json({ message: 'Protected route accessed successfully' });
|
64
|
+
});
|
65
|
+
|
66
|
+
// Example role-based protection
|
67
|
+
app.get('/api/admin', auth.requireRoles(['admin']), (req, res) => {
|
68
|
+
res.json({ message: 'Admin route accessed successfully' });
|
69
|
+
});
|
70
|
+
|
71
|
+
// Login route
|
72
|
+
app.post('/api/login', async (req, res) => {
|
73
|
+
const { username, password } = req.body;
|
74
|
+
|
75
|
+
// In a real app, fetch user from database
|
76
|
+
const user = { id: 1, username, roles: ['user'] };
|
77
|
+
const token = auth.generateToken(user);
|
78
|
+
|
79
|
+
res.json({ token, user: { id: user.id, username: user.username, roles: user.roles } });
|
80
|
+
});
|
81
|
+
}
|
82
|
+
|
14
83
|
// Serve static files from public directory
|
15
84
|
app.use(express.static(join(__dirname, 'public')));
|
16
85
|
|
@@ -23,6 +92,42 @@ app.get('/api/page-data', (req, res) => {
|
|
23
92
|
});
|
24
93
|
});
|
25
94
|
|
95
|
+
// Meta tag generation function (using local logic for simplicity)
|
96
|
+
async function generateMetaTags(pageContent) {
|
97
|
+
// Extract title from page content
|
98
|
+
const title = pageContent.split('\n')[0].replace(/[#*]/g, '').trim() ||
|
99
|
+
'Frontend Hamroun SSR Page';
|
100
|
+
|
101
|
+
// Generate description from content
|
102
|
+
const description = pageContent.substring(0, 150) + '...';
|
103
|
+
|
104
|
+
// Extract keywords
|
105
|
+
const keywords = pageContent
|
106
|
+
.toLowerCase()
|
107
|
+
.replace(/[^\w\s]/g, '')
|
108
|
+
.split(/\s+/)
|
109
|
+
.filter(w => w.length > 3)
|
110
|
+
.slice(0, 5)
|
111
|
+
.join(', ');
|
112
|
+
|
113
|
+
return {
|
114
|
+
title,
|
115
|
+
description,
|
116
|
+
keywords
|
117
|
+
};
|
118
|
+
}
|
119
|
+
|
120
|
+
// Helper function to check if file exists
|
121
|
+
async function fileExists(path) {
|
122
|
+
try {
|
123
|
+
const fs = await import('fs/promises');
|
124
|
+
await fs.access(path);
|
125
|
+
return true;
|
126
|
+
} catch {
|
127
|
+
return false;
|
128
|
+
}
|
129
|
+
}
|
130
|
+
|
26
131
|
// Implement basic SSR without relying on complex server functionality
|
27
132
|
app.get('*', async (req, res) => {
|
28
133
|
try {
|
@@ -60,6 +165,17 @@ app.get('*', async (req, res) => {
|
|
60
165
|
// Import the component
|
61
166
|
const { default: PageComponent } = await import(componentPath);
|
62
167
|
|
168
|
+
// Generate page content for meta tags
|
169
|
+
const pageContent = `
|
170
|
+
Frontend Hamroun SSR Page
|
171
|
+
This is a server-rendered page using the Frontend Hamroun framework.
|
172
|
+
Path: ${req.path}
|
173
|
+
Timestamp: ${new Date().toISOString()}
|
174
|
+
`;
|
175
|
+
|
176
|
+
// Generate meta tags
|
177
|
+
const metaTags = await generateMetaTags(pageContent);
|
178
|
+
|
63
179
|
// Render the component to string
|
64
180
|
const content = renderToString(PageComponent());
|
65
181
|
|
@@ -70,15 +186,36 @@ app.get('*', async (req, res) => {
|
|
70
186
|
<head>
|
71
187
|
<meta charset="UTF-8">
|
72
188
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
73
|
-
|
189
|
+
|
190
|
+
<!-- Generated Meta Tags -->
|
191
|
+
<title>${metaTags.title}</title>
|
192
|
+
<meta name="description" content="${metaTags.description}">
|
193
|
+
<meta name="keywords" content="${metaTags.keywords}">
|
194
|
+
|
195
|
+
<!-- Open Graph Meta Tags -->
|
196
|
+
<meta property="og:title" content="${metaTags.title}">
|
197
|
+
<meta property="og:description" content="${metaTags.description}">
|
198
|
+
<meta property="og:type" content="website">
|
199
|
+
<meta property="og:url" content="${req.protocol}://${req.get('host')}${req.originalUrl}">
|
200
|
+
|
74
201
|
<!-- Import Tailwind-like styles for quick styling -->
|
75
202
|
<link href="https://cdn.jsdelivr.net/npm/daisyui@3.7.4/dist/full.css" rel="stylesheet" type="text/css" />
|
76
203
|
<script src="https://cdn.tailwindcss.com"></script>
|
204
|
+
|
77
205
|
<!-- Client-side script for hydration -->
|
78
206
|
<script type="module" src="/assets/client.js"></script>
|
79
207
|
</head>
|
80
208
|
<body>
|
81
209
|
<div id="app">${content}</div>
|
210
|
+
|
211
|
+
<!-- Add initial state for hydration -->
|
212
|
+
<script>
|
213
|
+
window.__INITIAL_STATE__ = ${JSON.stringify({
|
214
|
+
path: req.path,
|
215
|
+
timestamp: new Date().toISOString(),
|
216
|
+
metaTags
|
217
|
+
})};
|
218
|
+
</script>
|
82
219
|
</body>
|
83
220
|
</html>
|
84
221
|
`);
|
@@ -100,24 +237,39 @@ app.get('*', async (req, res) => {
|
|
100
237
|
}
|
101
238
|
});
|
102
239
|
|
103
|
-
//
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
240
|
+
// Add error handler middleware
|
241
|
+
app.use(errorHandler);
|
242
|
+
|
243
|
+
// Add not found handler for API routes that weren't caught
|
244
|
+
app.use(notFoundHandler);
|
245
|
+
|
246
|
+
// Graceful shutdown function to close database connections
|
247
|
+
function gracefulShutdown() {
|
248
|
+
console.log('Shutting down server...');
|
249
|
+
|
250
|
+
// Close database connection if it exists
|
251
|
+
if (db) {
|
252
|
+
db.disconnect()
|
253
|
+
.then(() => console.log('Database disconnected'))
|
254
|
+
.catch(err => console.error('Error disconnecting from database:', err))
|
255
|
+
.finally(() => process.exit(0));
|
256
|
+
} else {
|
257
|
+
process.exit(0);
|
111
258
|
}
|
112
259
|
}
|
113
260
|
|
114
261
|
// Start the server
|
115
|
-
app.listen(port, () => {
|
262
|
+
const server = app.listen(port, () => {
|
116
263
|
console.log(`Server running at http://localhost:${port}`);
|
264
|
+
console.log(`Available API endpoints:`);
|
265
|
+
console.log(` - GET /api/page-data`);
|
266
|
+
console.log(` - POST /api/login`);
|
267
|
+
if (process.env.JWT_SECRET) {
|
268
|
+
console.log(` - GET /api/protected (requires authentication)`);
|
269
|
+
console.log(` - GET /api/admin (requires admin role)`);
|
270
|
+
}
|
117
271
|
});
|
118
272
|
|
119
273
|
// Handle graceful shutdown
|
120
|
-
process.on('SIGINT',
|
121
|
-
|
122
|
-
process.exit(0);
|
123
|
-
});
|
274
|
+
process.on('SIGINT', gracefulShutdown);
|
275
|
+
process.on('SIGTERM', gracefulShutdown);
|
@@ -14,10 +14,9 @@
|
|
14
14
|
"resolveJsonModule": true,
|
15
15
|
"isolatedModules": true,
|
16
16
|
"noEmit": true,
|
17
|
-
"jsx": "
|
17
|
+
"jsx": "preserve",
|
18
18
|
"jsxFactory": "createElement",
|
19
19
|
"jsxFragmentFactory": "Fragment"
|
20
20
|
},
|
21
|
-
"include": ["src"]
|
22
|
-
"references": [{ "path": "./tsconfig.server.json" }]
|
21
|
+
"include": ["src"]
|
23
22
|
}
|