@gov-cy/govcy-express-services 1.2.0 → 1.3.0-alpha.1

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 CHANGED
@@ -1740,6 +1740,7 @@ The validation rules for each element are defined in the `"validations` array fo
1740
1740
  - `date`: Date input (DD/MM/YYYY)
1741
1741
  - `dateISO`: ISO date input `YYYY-M-D`
1742
1742
  - `dateDMY`: European/Common Format date input `D/M/YYYY`
1743
+ - `maxCurrentYear`: Maximum current year input
1743
1744
  - `required`: Checks if the value is not null, undefined, or an empty string (after trimming).
1744
1745
  - `length`: Checks if the value has a maximum length passed in the `checkValue` parameter.
1745
1746
  - `regCheck`: Checks if the value matches the specified regular expression passed in the `checkValue` parameter.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gov-cy/govcy-express-services",
3
- "version": "1.2.0",
3
+ "version": "1.3.0-alpha.1",
4
4
  "description": "An Express-based system that dynamically renders services using @gov-cy/govcy-frontend-renderer and posts data to a submission API.",
5
5
  "author": "DMRID - DSF Team",
6
6
  "license": "MIT",
@@ -52,7 +52,7 @@
52
52
  },
53
53
  "dependencies": {
54
54
  "@gov-cy/dsf-email-templates": "^2.1.0",
55
- "@gov-cy/govcy-frontend-renderer": "^1.24.0",
55
+ "@gov-cy/govcy-frontend-renderer": "^1.26.0",
56
56
  "axios": "^1.9.0",
57
57
  "cookie-parser": "^1.4.7",
58
58
  "dotenv": "^16.3.1",
@@ -60,6 +60,7 @@
60
60
  "express-session": "^1.17.3",
61
61
  "form-data": "^4.0.4",
62
62
  "multer": "^2.0.2",
63
+ "nunjucks": "^3.2.4",
63
64
  "openid-client": "^6.3.4",
64
65
  "puppeteer": "^24.6.0"
65
66
  },
@@ -76,5 +77,8 @@
76
77
  },
77
78
  "engines": {
78
79
  "node": ">=18.0.0"
80
+ },
81
+ "overrides": {
82
+ "tar-fs": "^3.1.1"
79
83
  }
80
84
  }
package/src/index.mjs CHANGED
@@ -18,7 +18,7 @@ import { govcyCsrfMiddleware } from './middleware/govcyCsrf.mjs';
18
18
  import { govcySessionData } from './middleware/govcySessionData.mjs';
19
19
  import { govcyHttpErrorHandler } from './middleware/govcyHttpErrorHandler.mjs';
20
20
  import { govcyLanguageMiddleware } from './middleware/govcyLanguageMiddleware.mjs';
21
- import { requireAuth, naturalPersonPolicy,handleLoginRoute, handleSigninOidc, handleLogout } from './middleware/cyLoginAuth.mjs';
21
+ import { requireAuth, naturalPersonPolicy, handleLoginRoute, handleSigninOidc, handleLogout } from './middleware/cyLoginAuth.mjs';
22
22
  import { serviceConfigDataMiddleware } from './middleware/govcyConfigSiteData.mjs';
23
23
  import { govcyManifestHandler } from './middleware/govcyManifestHandler.mjs';
24
24
  import { govcyRoutePageHandler } from './middleware/govcyRoutePageHandler.mjs';
@@ -27,32 +27,34 @@ import { govcyLoadSubmissionData } from './middleware/govcyLoadSubmissionData.mj
27
27
  import { govcyFileUpload } from './middleware/govcyFileUpload.mjs';
28
28
  import { govcyFileDeletePageHandler, govcyFileDeletePostHandler } from './middleware/govcyFileDeleteHandler.mjs';
29
29
  import { govcyFileViewHandler } from './middleware/govcyFileViewHandler.mjs';
30
- import { isProdOrStaging , getEnvVariable, whatsIsMyEnvironment } from './utils/govcyEnvVariables.mjs';
30
+ import { govcyMultipleThingsAddHandler, govcyMultipleThingsEditHandler, govcyMultipleThingsAddPostHandler, govcyMultipleThingsEditPostHandler } from './middleware/govcyMultipleThingsItemPage.mjs';
31
+ import { govcyMultipleThingsDeletePageHandler, govcyMultipleThingsDeletePostHandler } from './middleware/govcyMultipleThingsDeleteHandler.mjs';
32
+ import { isProdOrStaging, getEnvVariable, whatsIsMyEnvironment } from './utils/govcyEnvVariables.mjs';
31
33
  import { logger } from "./utils/govcyLogger.mjs";
32
34
 
33
35
  import fs from 'fs';
34
36
 
35
- export default function initializeGovCyExpressService(){
37
+ export default function initializeGovCyExpressService() {
36
38
  const app = express();
37
39
 
38
40
  // Add this line before session middleware
39
41
  app.set('trust proxy', 1);
40
-
42
+
41
43
  // Get the directory name of the current module
42
44
  const __dirname = dirname(fileURLToPath(import.meta.url));
43
45
  // Construct the absolute path to local certificate files
44
46
  logger.debug('Current directory:', __dirname);
45
47
  logger.debug('Current working directory:', process.cwd());
46
- const certPath = join(process.cwd(),'server');
47
-
48
+ const certPath = join(process.cwd(), 'server');
49
+
48
50
  // Determine environment settings
49
51
  const ENV = whatsIsMyEnvironment();
50
52
  // Set port
51
53
  const PORT = getEnvVariable('PORT') || 44319;
52
54
  // Use HTTPS if isProdOrStaging or certificate files exist
53
55
  const USE_HTTPS = isProdOrStaging() || (fs.existsSync(certPath + '.cert') && fs.existsSync(certPath + '.key'));
54
-
55
-
56
+
57
+
56
58
  // Middleware
57
59
  // Enable parsing of URL-encoded data (data from HTML form submissions with application/x-www-form-urlencoded encoding)
58
60
  app.use(express.urlencoded({ extended: true }));
@@ -64,39 +66,39 @@ export default function initializeGovCyExpressService(){
64
66
  secret: getEnvVariable('SESSION_SECRET'), // Use environment variable or fallback for dev. To generate a secret, run: `node -e "console.log(require('crypto').randomBytes(32).toString('hex'));"`
65
67
  resave: false, // Prevents unnecessary session updates
66
68
  saveUninitialized: false, // Don't save empty sessions
67
- cookie: {
69
+ cookie: {
68
70
  secure: false, // Secure cookies only if HTTPS is used
69
71
  httpOnly: true, // Prevents XSS attacks
70
72
  maxAge: 1800000, // Session expires after 30 mins
71
- sameSite: 'lax' // Prevents CSRF by default
72
- }
73
+ sameSite: 'lax' // Prevents CSRF by default
74
+ }
73
75
  })
74
76
  );
75
77
  // Enable cookie parsing
76
- app.use(cookieParser());
78
+ app.use(cookieParser());
77
79
  // Apply language middleware
78
- app.use(govcyLanguageMiddleware);
80
+ app.use(govcyLanguageMiddleware);
79
81
  // Add request timing middleware
80
82
  app.use(requestTimer);
81
83
  // add csrf middleware
82
84
  app.use(govcyCsrfMiddleware);
83
85
  // Enable security headers
84
86
  app.use(noCacheAndSecurityHeaders);
85
-
87
+
86
88
  // 🔒 cyLogin ----------------------------------------
87
-
89
+
88
90
  // 🔒 -- ROUTE: Redirect to Login
89
- app.get('/login', handleLoginRoute() );
90
-
91
+ app.get('/login', handleLoginRoute());
92
+
91
93
  // 🔒 -- ROUTE: Handle login Callback
92
- app.get('/signin-oidc', handleSigninOidc() );
93
-
94
+ app.get('/signin-oidc', handleSigninOidc());
95
+
94
96
  // 🔒 -- ROUTE: Handle Logout
95
- app.get('/logout', handleLogout() );
96
-
97
+ app.get('/logout', handleLogout());
98
+
97
99
  //----------------------------------------------------------------------
98
-
99
-
100
+
101
+
100
102
  // 🛠️ Debugging routes -----------------------------------------------------
101
103
  // 🙍🏻‍♂️ -- ROUTE: Debugging route Protected Route
102
104
  // if (!isProdOrStaging()) {
@@ -114,72 +116,211 @@ export default function initializeGovCyExpressService(){
114
116
  // });
115
117
  // }
116
118
  //----------------------------------------------------------------------
117
-
118
-
119
+
120
+
119
121
  // ✅ Ensures session structure exists
120
- app.use(govcySessionData);
122
+ app.use(govcySessionData);
121
123
  // add logger middleware
122
124
  app.use(requestLogger);
123
-
125
+
124
126
  // Construct the absolute path to the public directory
125
127
  const publicPath = join(__dirname, 'public');
126
128
  // 🌐 -- ROUTE: Serve static files in the public directory. Route for `/js/`
127
129
  app.use(express.static(publicPath));
128
-
130
+
129
131
  // 🏡 -- ROUTE: handle the route `/`
130
- app.get('/', govcyRoutePageHandler);
132
+ app.get('/', govcyRoutePageHandler);
131
133
 
132
134
  // 📝 -- ROUTE: Serve manifest.json dynamically for each site
133
135
  app.get('/:siteId/manifest.json', serviceConfigDataMiddleware, govcyManifestHandler());
134
136
 
135
137
  // 🗃️ -- ROUTE: Handle POST requests for file uploads for a page.
136
- app.post('/apis/:siteId/:pageUrl/upload',
137
- serviceConfigDataMiddleware,
138
+ app.post('/apis/:siteId/:pageUrl/upload',
139
+ serviceConfigDataMiddleware,
138
140
  requireAuth, // UNCOMMENT
139
141
  naturalPersonPolicy, // UNCOMMENT
140
142
  govcyServiceEligibilityHandler(true), // UNCOMMENT
141
143
  govcyFileUpload);
142
-
144
+
145
+ // 🗃️ -- ROUTE: Handle POST requests for file uploads inside multipleThings (add)
146
+ app.post('/apis/:siteId/:pageUrl/multiple/add/upload',
147
+ serviceConfigDataMiddleware,
148
+ requireAuth, // UNCOMMENT
149
+ naturalPersonPolicy, // UNCOMMENT
150
+ govcyServiceEligibilityHandler(true), // UNCOMMENT
151
+ govcyFileUpload
152
+ );
153
+
154
+ // 🗃️ -- ROUTE: Handle POST requests for file uploads inside multipleThings (edit)
155
+ app.post('/apis/:siteId/:pageUrl/multiple/edit/:index/upload',
156
+ serviceConfigDataMiddleware,
157
+ requireAuth, // UNCOMMENT
158
+ naturalPersonPolicy, // UNCOMMENT
159
+ govcyServiceEligibilityHandler(true), // UNCOMMENT
160
+ govcyFileUpload
161
+ );
162
+
163
+ // View (multipleThings draft)
164
+ app.get('/:siteId/:pageUrl/multiple/add/view-file/:elementName',
165
+ serviceConfigDataMiddleware,
166
+ requireAuth,
167
+ naturalPersonPolicy,
168
+ govcyServiceEligibilityHandler(true),
169
+ govcyFileViewHandler());
170
+
171
+ // ❌🗃️ -- ROUTE: Delete file during multipleThings ADD (before dynamic route)
172
+ app.get('/:siteId/:pageUrl/multiple/add/delete-file/:elementName',
173
+ serviceConfigDataMiddleware,
174
+ requireAuth,
175
+ naturalPersonPolicy,
176
+ govcyServiceEligibilityHandler(),
177
+ govcyLoadSubmissionData(),
178
+ govcyFileDeletePageHandler(),
179
+ renderGovcyPage()
180
+ );
181
+
182
+ // ❌🗃️ -- ROUTE: Delete file during multipleThings EDIT (before dynamic route)
183
+ app.get('/:siteId/:pageUrl/multiple/edit/:index/delete-file/:elementName',
184
+ serviceConfigDataMiddleware,
185
+ requireAuth,
186
+ naturalPersonPolicy,
187
+ govcyServiceEligibilityHandler(),
188
+ govcyLoadSubmissionData(),
189
+ govcyFileDeletePageHandler(),
190
+ renderGovcyPage()
191
+ );
192
+
193
+
194
+ // ❌🗃️📥 -- ROUTE: Handle POST requests for delete file in multipleThings ADD
195
+ app.post('/:siteId/:pageUrl/multiple/add/delete-file/:elementName',
196
+ serviceConfigDataMiddleware,
197
+ requireAuth,
198
+ naturalPersonPolicy,
199
+ govcyServiceEligibilityHandler(true),
200
+ govcyFileDeletePostHandler()
201
+ );
202
+
203
+ // ❌🗃️📥 -- ROUTE: Handle POST requests for delete file in multipleThings EDIT
204
+ app.post('/:siteId/:pageUrl/multiple/edit/:index/delete-file/:elementName',
205
+ serviceConfigDataMiddleware,
206
+ requireAuth,
207
+ naturalPersonPolicy,
208
+ govcyServiceEligibilityHandler(true),
209
+ govcyFileDeletePostHandler()
210
+ );
211
+
212
+
213
+ // View (multipleThings edit)
214
+ app.get('/:siteId/:pageUrl/multiple/edit/:index/view-file/:elementName',
215
+ serviceConfigDataMiddleware,
216
+ requireAuth,
217
+ naturalPersonPolicy,
218
+ govcyServiceEligibilityHandler(true),
219
+ govcyFileViewHandler());
220
+
143
221
  // 🏠 -- ROUTE: Handle route with only siteId (/:siteId or /:siteId/)
144
- app.get('/:siteId', serviceConfigDataMiddleware, requireAuth, naturalPersonPolicy, govcyServiceEligibilityHandler(true),govcyLoadSubmissionData(),govcyPageHandler(), renderGovcyPage());
145
-
222
+ app.get('/:siteId', serviceConfigDataMiddleware, requireAuth, naturalPersonPolicy, govcyServiceEligibilityHandler(true), govcyLoadSubmissionData(), govcyPageHandler(), renderGovcyPage());
223
+
146
224
  // 👀 -- ROUTE: Add Review Page Route (BEFORE the dynamic route)
147
- app.get('/:siteId/review',serviceConfigDataMiddleware, requireAuth, naturalPersonPolicy, govcyServiceEligibilityHandler(),govcyLoadSubmissionData(), govcyReviewPageHandler(), renderGovcyPage());
148
-
225
+ app.get('/:siteId/review', serviceConfigDataMiddleware, requireAuth, naturalPersonPolicy, govcyServiceEligibilityHandler(), govcyLoadSubmissionData(), govcyReviewPageHandler(), renderGovcyPage());
226
+
149
227
  // ✅📄 -- ROUTE: Add Success PDF Route (BEFORE the dynamic route)
150
- app.get('/:siteId/success/pdf',serviceConfigDataMiddleware, requireAuth, naturalPersonPolicy, govcyServiceEligibilityHandler(), govcySuccessPageHandler(true), govcyPDFRender());
151
-
228
+ app.get('/:siteId/success/pdf', serviceConfigDataMiddleware, requireAuth, naturalPersonPolicy, govcyServiceEligibilityHandler(), govcySuccessPageHandler(true), govcyPDFRender());
229
+
152
230
  // ✅ -- ROUTE: Add Success Page Route (BEFORE the dynamic route)
153
- app.get('/:siteId/success',serviceConfigDataMiddleware, requireAuth, naturalPersonPolicy, govcyServiceEligibilityHandler(), govcySuccessPageHandler(), renderGovcyPage());
154
-
231
+ app.get('/:siteId/success', serviceConfigDataMiddleware, requireAuth, naturalPersonPolicy, govcyServiceEligibilityHandler(), govcySuccessPageHandler(), renderGovcyPage());
232
+
233
+
155
234
  // 👀🗃️ -- ROUTE: View file (BEFORE the dynamic route)
156
235
  app.get('/:siteId/:pageUrl/view-file/:elementName', serviceConfigDataMiddleware, requireAuth, naturalPersonPolicy, govcyServiceEligibilityHandler(), govcyLoadSubmissionData(), govcyFileViewHandler());
157
236
 
158
237
  // ❌🗃️ -- ROUTE: Delete file (BEFORE the dynamic route)
159
238
  app.get('/:siteId/:pageUrl/delete-file/:elementName', serviceConfigDataMiddleware, requireAuth, naturalPersonPolicy, govcyServiceEligibilityHandler(), govcyLoadSubmissionData(), govcyFileDeletePageHandler(), renderGovcyPage());
160
-
239
+
240
+ // ➕ -- ROUTE: Add item page (BEFORE the generic dynamic route)
241
+ app.get('/:siteId/:pageUrl/multiple/add',
242
+ serviceConfigDataMiddleware,
243
+ requireAuth,
244
+ naturalPersonPolicy,
245
+ govcyServiceEligibilityHandler(true),
246
+ govcyLoadSubmissionData(),
247
+ govcyMultipleThingsAddHandler(),
248
+ renderGovcyPage()
249
+ );
250
+
251
+
252
+ // ➕ -- ROUTE: Add item POST (BEFORE the generic POST)
253
+ app.post('/:siteId/:pageUrl/multiple/add',
254
+ serviceConfigDataMiddleware,
255
+ requireAuth,
256
+ naturalPersonPolicy,
257
+ govcyServiceEligibilityHandler(true),
258
+ govcyMultipleThingsAddPostHandler()
259
+ );
260
+
261
+ // ✏️ -- ROUTE: Edit item page (BEFORE the generic dynamic route)
262
+ app.get('/:siteId/:pageUrl/multiple/edit/:index',
263
+ serviceConfigDataMiddleware,
264
+ requireAuth,
265
+ naturalPersonPolicy,
266
+ govcyServiceEligibilityHandler(true),
267
+ govcyLoadSubmissionData(),
268
+ govcyMultipleThingsEditHandler(),
269
+ renderGovcyPage()
270
+ );
271
+
272
+ // 🗃️ -- ROUTE: Handle POST requests for multipleThings EDIT item
273
+ app.post('/:siteId/:pageUrl/multiple/edit/:index',
274
+ serviceConfigDataMiddleware,
275
+ requireAuth,
276
+ naturalPersonPolicy,
277
+ govcyServiceEligibilityHandler(true),
278
+ govcyMultipleThingsEditPostHandler()
279
+ );
280
+
281
+ // ❌🗃️ -- ROUTE: Delete multipleThings item (BEFORE the dynamic route)
282
+ app.get('/:siteId/:pageUrl/multiple/delete/:index',
283
+ serviceConfigDataMiddleware,
284
+ requireAuth,
285
+ naturalPersonPolicy,
286
+ govcyServiceEligibilityHandler(),
287
+ govcyLoadSubmissionData(),
288
+ govcyMultipleThingsDeletePageHandler(),
289
+ renderGovcyPage()
290
+ );
291
+
292
+ // ❌🗃️📥 -- ROUTE: Handle POST requests for delete multipleThings item
293
+ app.post('/:siteId/:pageUrl/multiple/delete/:index',
294
+ serviceConfigDataMiddleware,
295
+ requireAuth,
296
+ naturalPersonPolicy,
297
+ govcyServiceEligibilityHandler(true),
298
+ govcyMultipleThingsDeletePostHandler()
299
+ );
300
+
301
+
161
302
  // 📝 -- ROUTE: Dynamic route to render pages based on siteId and pageUrl, using govcyPageHandler middleware
162
303
  app.get('/:siteId/:pageUrl', serviceConfigDataMiddleware, requireAuth, naturalPersonPolicy, govcyServiceEligibilityHandler(true), govcyLoadSubmissionData(), govcyPageHandler(), renderGovcyPage());
163
-
304
+
164
305
  // ❌🗃️📥 -- ROUTE: Handle POST requests for delete file
165
306
  app.post('/:siteId/:pageUrl/delete-file/:elementName', serviceConfigDataMiddleware, requireAuth, naturalPersonPolicy, govcyServiceEligibilityHandler(true), govcyFileDeletePostHandler());
166
-
307
+
167
308
  // 📥 -- ROUTE: Handle POST requests for review page. The `submit` action
168
309
  app.post('/:siteId/review', serviceConfigDataMiddleware, requireAuth, naturalPersonPolicy, govcyServiceEligibilityHandler(), govcyReviewPostHandler());
169
-
310
+
170
311
  // 👀📥 -- ROUTE: Handle POST requests (Form Submissions) based on siteId and pageUrl, using govcyFormsPostHandler middleware
171
312
  app.post('/:siteId/:pageUrl', serviceConfigDataMiddleware, requireAuth, naturalPersonPolicy, govcyServiceEligibilityHandler(true), govcyFormsPostHandler());
172
-
313
+
173
314
  // post for /:siteId/review
174
-
315
+
175
316
  // 🔹 Catch 404 errors (must be after all routes)
176
317
  app.use((req, res, next) => {
177
318
  next({ status: 404, message: "Page not found" });
178
319
  });
179
-
320
+
180
321
  // 🔹 Centralized error handling (must be the LAST middleware)
181
322
  app.use(govcyHttpErrorHandler);
182
-
323
+
183
324
  let server = null;
184
325
 
185
326
  return {
@@ -199,7 +340,7 @@ export default function initializeGovCyExpressService(){
199
340
  logger.info(`⚡ Server running at http://localhost:${PORT} (${ENV})`);
200
341
  });
201
342
  }
202
- },
343
+ },
203
344
  stopServer: () => {
204
345
  if (server) {
205
346
  server.close(() => {
@@ -30,6 +30,10 @@ export async function serviceConfigDataMiddleware(req, res, next) {
30
30
  });
31
31
  }
32
32
 
33
+ //replace the globalLang with the site.lang
34
+ req.globalLang = req.serviceData.site.lang;
35
+
36
+
33
37
  next();
34
38
  } catch (error) {
35
39
  return next(error)
@@ -47,14 +47,16 @@ export function govcyFileDeletePageHandler() {
47
47
  return handleMiddlewareError(`File input [${elementName}] does not have a label`, 404, next);
48
48
  }
49
49
 
50
- //get element data
51
- const elementData = dataLayer.getFormDataValue(req.session, siteId, pageUrl, elementName)
52
-
53
- // If the element data is not found, return an error response
54
- if (!elementData || !elementData?.sha256 || !elementData?.fileId) {
50
+ // --- Resolve file element data (normal + multipleThings add/edit) ---
51
+ const { index } = req.params;
52
+ const elementData = dataLayer.getFormDataValue(req.session, siteId, pageUrl, elementName, index);
53
+
54
+ // Guard if still nothing found
55
+ if (!elementData || !elementData.fileId || !elementData.sha256) {
55
56
  return handleMiddlewareError(`File input [${elementName}] data not found on this page`, 404, next);
56
57
  }
57
58
 
59
+
58
60
  // Deep copy page title (so we don’t mutate template)
59
61
  const pageTitle = JSON.parse(JSON.stringify(govcyResources.staticResources.text.deleteFileTitle));
60
62
 
@@ -92,7 +94,7 @@ export function govcyFileDeletePageHandler() {
92
94
  const showSameFileWarning = dataLayer.isFileUsedInSiteInputDataAgain(
93
95
  req.session,
94
96
  siteId,
95
- elementData
97
+ elementData
96
98
  );
97
99
 
98
100
  // Construct page title
@@ -119,12 +121,23 @@ export function govcyFileDeletePageHandler() {
119
121
  }
120
122
  };
121
123
  //-------------
122
-
124
+ // Build proper action based on context (normal / multiple add / multiple edit)
125
+ let actionPath;
126
+ if (typeof req.params.index !== "undefined") {
127
+ // multiple edit
128
+ actionPath = `${pageUrl}/multiple/edit/${req.params.index}/delete-file/${elementName}`;
129
+ } else if (req.originalUrl.includes("/multiple/add")) {
130
+ // multiple add
131
+ actionPath = `${pageUrl}/multiple/add/delete-file/${elementName}`;
132
+ } else {
133
+ // normal page
134
+ actionPath = `${pageUrl}/delete-file/${elementName}`;
135
+ }
123
136
  // Construct submit button
124
137
  const formElement = {
125
138
  element: "form",
126
139
  params: {
127
- action: govcyResources.constructPageUrl(siteId, `${pageUrl}/delete-file/${elementName}`, (req.query.route === "review" ? "review" : "")),
140
+ action: govcyResources.constructPageUrl(siteId, actionPath, (req.query.route === "review" ? "review" : "")),
128
141
  method: "POST",
129
142
  elements: [
130
143
  pageRadios,
@@ -159,11 +172,6 @@ export function govcyFileDeletePageHandler() {
159
172
  // Append generated summary list to the page template
160
173
  pageTemplate.sections.push({ name: "main", elements: mainElements });
161
174
 
162
- //if user is logged in add he user bane section in the page template
163
- if (dataLayer.getUser(req.session)) {
164
- pageTemplate.sections.push(govcyResources.userNameSection(dataLayer.getUser(req.session).name)); // Add user name section
165
- }
166
-
167
175
  //prepare pageData
168
176
  pageData.site = serviceCopy.site;
169
177
  pageData.pageData.title = pageTitle;
@@ -215,16 +223,30 @@ export function govcyFileDeletePostHandler() {
215
223
  return handleMiddlewareError(`File input [${elementName}] not allowed on this page`, 404, next);
216
224
  }
217
225
 
218
- //get element data
219
- const elementData = dataLayer.getFormDataValue(req.session, siteId, pageUrl, elementName)
220
-
221
- // If the element data is not found, return an error response
222
- if (!elementData || !elementData?.sha256 || !elementData?.fileId) {
226
+ // --- Resolve file element data (normal + multipleThings add/edit) ---
227
+ const { index } = req.params;
228
+ const elementData = dataLayer.getFormDataValue(req.session, siteId, pageUrl, elementName, index);
229
+
230
+ // Guard if still nothing found
231
+ if (!elementData || !elementData.fileId || !elementData.sha256) {
223
232
  return handleMiddlewareError(`File input [${elementName}] data not found on this page`, 404, next);
224
233
  }
225
234
 
235
+ // Build proper action based on context (normal / multiple add / multiple edit)
236
+ let actionPath;
237
+ if (typeof req.params.index !== "undefined") {
238
+ // multiple edit
239
+ actionPath = `${pageUrl}/multiple/edit/${req.params.index}`;
240
+ } else if (req.originalUrl.includes("/multiple/add")) {
241
+ // multiple add
242
+ actionPath = `${pageUrl}/multiple/add`;
243
+ } else {
244
+ // normal page
245
+ actionPath = `${pageUrl}`;
246
+ }
247
+
226
248
  // the page base return url
227
- const pageBaseReturnUrl = `http://localhost:3000/${siteId}/${pageUrl}`;
249
+ const pageBaseReturnUrl = `http://localhost:3000/${siteId}/${actionPath}`;
228
250
 
229
251
  //check if input `deleteFile` has a value
230
252
  if (!req?.body?.deleteFile ||
@@ -17,13 +17,26 @@ export const govcyFileUpload = [
17
17
  upload.single('file'), // multer parses the uploaded file and stores it in req.file
18
18
 
19
19
  async function govcyUploadHandler(req, res) {
20
+ let mode = "single";
21
+ let index = null;
22
+
23
+ // Detect MultipleThings modes based on URL
24
+ if (req.originalUrl.includes("/multiple/add")) {
25
+ mode = "multipleThingsDraft";
26
+ } else if (req.originalUrl.includes("/multiple/edit/")) {
27
+ mode = "multipleThingsEdit";
28
+ index = parseInt(req.params.index, 10);
29
+ }
30
+
20
31
  const result = await handleFileUpload({
21
32
  service: req.serviceData,
22
33
  store: req.session,
23
34
  siteId: req.params.siteId,
24
35
  pageUrl: req.params.pageUrl,
25
36
  elementName: req.body?.elementName,
26
- file: req.file
37
+ file: req.file,
38
+ mode,
39
+ index
27
40
  });
28
41
 
29
42
  if (result.status !== 200) {
@@ -13,6 +13,17 @@ export function govcyFileViewHandler() {
13
13
  try {
14
14
  const { siteId, pageUrl, elementName } = req.params;
15
15
 
16
+ // Detect MultipleThings modes based on URL
17
+ let mode = "single";
18
+ let index = null;
19
+
20
+ if (req.originalUrl.includes("/multiple/add")) {
21
+ mode = "multipleThingsDraft";
22
+ } else if (req.originalUrl.includes("/multiple/edit/")) {
23
+ mode = "multipleThingsEdit";
24
+ index = parseInt(req.params.index, 10);
25
+ }
26
+
16
27
  // Create a deep copy of the service to avoid modifying the original
17
28
  let serviceCopy = req.serviceData;
18
29
 
@@ -64,7 +75,15 @@ export function govcyFileViewHandler() {
64
75
  }
65
76
 
66
77
  //get element data
67
- const elementData = dataLayer.getFormDataValue(req.session, siteId, pageUrl, elementName)
78
+ let elementData;
79
+ if (mode === "single") {
80
+ elementData = dataLayer.getFormDataValue(req.session, siteId, pageUrl, elementName);
81
+ } else if (mode === "multipleThingsDraft") {
82
+ elementData = dataLayer.getMultipleDraft(req.session, siteId, pageUrl)?.[elementName];
83
+ } else if (mode === "multipleThingsEdit") {
84
+ const items = dataLayer.getPageData(req.session, siteId, pageUrl) || [];
85
+ elementData = items[index]?.[elementName];
86
+ }
68
87
 
69
88
  // If the element data is not found, return an error response
70
89
  if (!elementData || !elementData?.sha256 || !elementData?.fileId) {