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.
@@ -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
- <title>Frontend Hamroun SSR</title>
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
- // Helper function to check if file exists
104
- async function fileExists(path) {
105
- try {
106
- const fs = await import('fs/promises');
107
- await fs.access(path);
108
- return true;
109
- } catch {
110
- return false;
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
- console.log('Shutting down server...');
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": "react",
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
  }