@aneuhold/be-ts-db-lib 4.2.21 → 4.2.23

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/CHANGELOG.md CHANGED
@@ -5,6 +5,24 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## 🔖 [4.2.23] (2026-04-15)
9
+
10
+ ### ✅ Added
11
+
12
+ - `WorkoutExerciseRepository.buildExerciseCTOs` now runs a third parallel aggregation pipeline to populate `lastSessionExercise`/`lastSessionSets` from the true latest completed session (any cycle type, including deloads and free-form).
13
+
14
+ ### 🏗️ Changed
15
+
16
+ - Repository now populates `lastAccumulationSessionExercise`/`lastAccumulationSessionSets` alongside `lastSessionExercise`/`lastSessionSets` in each `WorkoutExerciseCTO`.
17
+ - Updated dependencies on `@aneuhold/be-ts-lib` to `^3.1.10` and `@aneuhold/core-ts-db-lib` to `^5.0.5`.
18
+
19
+ ## 🔖 [4.2.22] (2026-03-26)
20
+
21
+ ### 🏗️ Changed
22
+
23
+ - Refactored `MigrationService` to migrate `DashboardUserConfig` records instead of `User.projectAccess` fields; now backfills `enableAdminPage` and `enabledFeatures.adminPage` with safe defaults.
24
+ - Updated dependencies on `@aneuhold/be-ts-lib` to `^3.1.9` and `@aneuhold/core-ts-db-lib` to `^5.0.4`.
25
+
8
26
  ## 🔖 [4.2.21] (2026-03-20)
9
27
 
10
28
  ### 🏗️ Changed
@@ -371,7 +389,8 @@ Updated dependencies: now requires `@aneuhold/core-ts-db-lib@^3.0.0`, `@aneuhold
371
389
  - Updated workflow permissions to allow repository write access
372
390
 
373
391
  <!-- Link References -->
374
-
392
+ [4.2.23]: https://github.com/aneuhold/ts-libs/compare/be-ts-db-lib-v4.2.22...be-ts-db-lib-v4.2.23
393
+ [4.2.22]: https://github.com/aneuhold/ts-libs/compare/be-ts-db-lib-v4.2.21...be-ts-db-lib-v4.2.22
375
394
  [4.2.21]: https://github.com/aneuhold/ts-libs/compare/be-ts-db-lib-v4.2.20...be-ts-db-lib-v4.2.21
376
395
  [4.2.20]: https://github.com/aneuhold/ts-libs/compare/be-ts-db-lib-v4.2.19...be-ts-db-lib-v4.2.20
377
396
  [4.2.19]: https://github.com/aneuhold/ts-libs/compare/be-ts-db-lib-v4.2.18...be-ts-db-lib-v4.2.19
@@ -13,9 +13,12 @@ export default class WorkoutExerciseRepository extends WorkoutBaseWithUserIdRepo
13
13
  static getRepo(): WorkoutExerciseRepository;
14
14
  /**
15
15
  * Builds {@link WorkoutExerciseCTO} objects for all exercises belonging to a user.
16
- * Uses two parallel MongoDB aggregation pipelines:
16
+ * Uses three parallel MongoDB aggregation pipelines:
17
17
  * - Pipeline A: exercise + equipmentType + bestCalibration + bestSet
18
- * - Pipeline B: lastSessionExercise + lastSessionSets (from most recent non-deload session)
18
+ * - Pipeline B (true latest): lastSessionExercise + lastSessionSets from the most
19
+ * recent completed session, regardless of cycle type / deload status.
20
+ * - Pipeline C (accumulation): lastAccumulationSessionExercise +
21
+ * lastAccumulationSessionSets from the most recent completed non-deload session.
19
22
  *
20
23
  * @param userId The user whose exercise CTOs to build.
21
24
  */
@@ -1 +1 @@
1
- {"version":3,"file":"WorkoutExerciseRepository.d.ts","sourceRoot":"","sources":["../../../src/repositories/workout/WorkoutExerciseRepository.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,IAAI,EAEJ,eAAe,EAEf,kBAAkB,EAGnB,MAAM,0BAA0B,CAAC;AAWlC,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AACnC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2CAA2C,CAAC;AAE/E,OAAO,+BAA+B,MAAM,sCAAsC,CAAC;AAGnF;;GAEG;AACH,MAAM,CAAC,OAAO,OAAO,yBAA0B,SAAQ,+BAA+B,CAAC,eAAe,CAAC;IACrG,OAAO,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAA4B;IAE7D,OAAO;IAIP,MAAM,CAAC,uBAAuB,IAAI,aAAa,CAAC,IAAI,CAAC;IAYrD,SAAS,CAAC,gBAAgB,IAAI,IAAI;WAIpB,OAAO,IAAI,yBAAyB;IAOlD;;;;;;;OAOG;IACG,wBAAwB,CAAC,MAAM,EAAE,IAAI,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC;CAkN5E"}
1
+ {"version":3,"file":"WorkoutExerciseRepository.d.ts","sourceRoot":"","sources":["../../../src/repositories/workout/WorkoutExerciseRepository.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,IAAI,EAEJ,eAAe,EAEf,kBAAkB,EAGnB,MAAM,0BAA0B,CAAC;AAWlC,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAEnC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2CAA2C,CAAC;AAE/E,OAAO,+BAA+B,MAAM,sCAAsC,CAAC;AAGnF;;GAEG;AACH,MAAM,CAAC,OAAO,OAAO,yBAA0B,SAAQ,+BAA+B,CAAC,eAAe,CAAC;IACrG,OAAO,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAA4B;IAE7D,OAAO;IAIP,MAAM,CAAC,uBAAuB,IAAI,aAAa,CAAC,IAAI,CAAC;IAYrD,SAAS,CAAC,gBAAgB,IAAI,IAAI;WAIpB,OAAO,IAAI,yBAAyB;IAOlD;;;;;;;;;;OAUG;IACG,wBAAwB,CAAC,MAAM,EAAE,IAAI,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC;CA+N5E"}
@@ -32,9 +32,12 @@ export default class WorkoutExerciseRepository extends WorkoutBaseWithUserIdRepo
32
32
  }
33
33
  /**
34
34
  * Builds {@link WorkoutExerciseCTO} objects for all exercises belonging to a user.
35
- * Uses two parallel MongoDB aggregation pipelines:
35
+ * Uses three parallel MongoDB aggregation pipelines:
36
36
  * - Pipeline A: exercise + equipmentType + bestCalibration + bestSet
37
- * - Pipeline B: lastSessionExercise + lastSessionSets (from most recent non-deload session)
37
+ * - Pipeline B (true latest): lastSessionExercise + lastSessionSets from the most
38
+ * recent completed session, regardless of cycle type / deload status.
39
+ * - Pipeline C (accumulation): lastAccumulationSessionExercise +
40
+ * lastAccumulationSessionSets from the most recent completed non-deload session.
38
41
  *
39
42
  * @param userId The user whose exercise CTOs to build.
40
43
  */
@@ -114,107 +117,123 @@ export default class WorkoutExerciseRepository extends WorkoutBaseWithUserIdRepo
114
117
  }
115
118
  ])
116
119
  .toArray();
117
- // Pipeline B: lastSessionExercise + lastFirstSet per exercise
118
- const pipelineBPromise = collection
119
- .aggregate([
120
- // Start with all completed sessions for this user
121
- { $match: { docType: WorkoutSession_docType, userId, complete: true } },
122
- // Most recent sessions first
123
- { $sort: { startTime: -1 } },
124
- // Join each session's session exercises
125
- {
126
- $lookup: {
127
- from: collName,
128
- let: { sessId: '$_id' },
129
- pipeline: [
130
- {
131
- $match: {
132
- $expr: { $eq: ['$workoutSessionId', '$$sessId'] },
133
- docType: WorkoutSessionExercise_docType
134
- }
120
+ // Builds the pipeline that finds, per exercise, the most recent session
121
+ // exercise from a completed session — optionally restricted to non-deload
122
+ // sessions (where plannedRir is not null on at least one set).
123
+ const buildLastSessionPipeline = (accumulationOnly) => {
124
+ const accumulationFilter = accumulationOnly
125
+ ? [
126
+ // Check for non-deload sets (plannedRir is not null)
127
+ {
128
+ $lookup: {
129
+ from: collName,
130
+ let: { seId: '$_se._id' },
131
+ pipeline: [
132
+ {
133
+ $match: {
134
+ $expr: { $eq: ['$workoutSessionExerciseId', '$$seId'] },
135
+ docType: WorkoutSet_docType,
136
+ plannedRir: { $ne: null }
137
+ }
138
+ },
139
+ { $limit: 1 }
140
+ ],
141
+ as: '_nonDeload'
135
142
  }
136
- ],
137
- as: '_se'
138
- }
139
- },
140
- // One row per session exercise (unwind coalescence optimization)
141
- { $unwind: '$_se' },
142
- // Check for non-deload sets (plannedRir is not null)
143
- {
144
- $lookup: {
145
- from: collName,
146
- let: { seId: '$_se._id' },
147
- pipeline: [
148
- {
149
- $match: {
150
- $expr: { $eq: ['$workoutSessionExerciseId', '$$seId'] },
151
- docType: WorkoutSet_docType,
152
- plannedRir: { $ne: null }
153
- }
154
- },
155
- { $limit: 1 }
156
- ],
157
- as: '_nonDeload'
158
- }
159
- },
160
- // Keep only session exercises with at least one non-deload set
161
- { $match: { '_nonDeload.0': { $exists: true } } },
162
- // Per exercise, keep only the most recent session exercise ($first after sort)
163
- {
164
- $group: {
165
- _id: '$_se.workoutExerciseId',
166
- lastSessionExercise: { $first: '$_se' }
167
- }
168
- },
169
- // Join all sets from the session exercise's setOrder
170
- {
171
- $lookup: {
172
- from: collName,
173
- let: { setIds: '$lastSessionExercise.setOrder' },
174
- pipeline: [
175
- {
176
- $match: {
177
- $expr: { $in: ['$_id', '$$setIds'] },
178
- docType: WorkoutSet_docType
143
+ },
144
+ // Keep only session exercises with at least one non-deload set
145
+ { $match: { '_nonDeload.0': { $exists: true } } }
146
+ ]
147
+ : [];
148
+ return [
149
+ // Start with all completed sessions for this user
150
+ { $match: { docType: WorkoutSession_docType, userId, complete: true } },
151
+ // Most recent sessions first
152
+ { $sort: { startTime: -1 } },
153
+ // Join each session's session exercises
154
+ {
155
+ $lookup: {
156
+ from: collName,
157
+ let: { sessId: '$_id' },
158
+ pipeline: [
159
+ {
160
+ $match: {
161
+ $expr: { $eq: ['$workoutSessionId', '$$sessId'] },
162
+ docType: WorkoutSessionExercise_docType
163
+ }
179
164
  }
180
- },
181
- // Preserve the original setOrder by sorting on index position
182
- {
183
- $addFields: {
184
- _sortIdx: { $indexOfArray: ['$$setIds', '$_id'] }
185
- }
186
- },
187
- { $sort: { _sortIdx: 1 } },
188
- { $project: { _sortIdx: 0 } }
189
- ],
190
- as: '_lastSessionSetsArr'
165
+ ],
166
+ as: '_se'
167
+ }
168
+ },
169
+ // One row per session exercise (unwind coalescence optimization)
170
+ { $unwind: '$_se' },
171
+ ...accumulationFilter,
172
+ // Per exercise, keep only the most recent session exercise ($first after sort)
173
+ {
174
+ $group: {
175
+ _id: '$_se.workoutExerciseId',
176
+ lastSessionExercise: { $first: '$_se' }
177
+ }
178
+ },
179
+ // Join all sets from the session exercise's setOrder
180
+ {
181
+ $lookup: {
182
+ from: collName,
183
+ let: { setIds: '$lastSessionExercise.setOrder' },
184
+ pipeline: [
185
+ {
186
+ $match: {
187
+ $expr: { $in: ['$_id', '$$setIds'] },
188
+ docType: WorkoutSet_docType
189
+ }
190
+ },
191
+ // Preserve the original setOrder by sorting on index position
192
+ {
193
+ $addFields: {
194
+ _sortIdx: { $indexOfArray: ['$$setIds', '$_id'] }
195
+ }
196
+ },
197
+ { $sort: { _sortIdx: 1 } },
198
+ { $project: { _sortIdx: 0 } }
199
+ ],
200
+ as: '_lastSessionSetsArr'
201
+ }
191
202
  }
192
- }
193
- ])
203
+ ];
204
+ };
205
+ // Pipeline B: most recent session exercise per exercise (any cycle type)
206
+ const pipelineBPromise = collection
207
+ .aggregate(buildLastSessionPipeline(false))
194
208
  .toArray();
195
- const [rawExercises, rawLastSessions] = await Promise.all([pipelineAPromise, pipelineBPromise]);
196
- // Build lookup map from Pipeline B
197
- const lastSessionMap = new Map();
198
- for (const row of rawLastSessions) {
199
- lastSessionMap.set(row._id, {
200
- lastSessionExercise: row.lastSessionExercise,
201
- lastSessionSets: row._lastSessionSetsArr
202
- });
203
- }
209
+ // Pipeline C: most recent accumulation (non-deload) session exercise per exercise
210
+ const pipelineCPromise = collection
211
+ .aggregate(buildLastSessionPipeline(true))
212
+ .toArray();
213
+ const [rawExercises, rawLastSessions, rawLastAccumulationSessions] = await Promise.all([
214
+ pipelineAPromise,
215
+ pipelineBPromise,
216
+ pipelineCPromise
217
+ ]);
218
+ const lastSessionMap = new Map(rawLastSessions.map((row) => [row._id, row]));
219
+ const lastAccumulationMap = new Map(rawLastAccumulationSessions.map((row) => [row._id, row]));
204
220
  // Assemble and validate CTOs
205
221
  return rawExercises.map((raw) => {
206
222
  const { _equipArr, _bestCalArr, _bestSetArr, ...exerciseFields } = raw;
207
223
  if (_equipArr.length === 0) {
208
224
  throw new Error(`Equipment type not found for exercise ${raw._id}`);
209
225
  }
210
- const lastData = lastSessionMap.get(raw._id);
226
+ const lastRow = lastSessionMap.get(raw._id);
227
+ const lastAccumulationRow = lastAccumulationMap.get(raw._id);
211
228
  return WorkoutExerciseCTOSchema.parse({
212
229
  ...exerciseFields,
213
230
  equipmentType: _equipArr[0],
214
231
  bestCalibration: _bestCalArr[0] ?? null,
215
232
  bestSet: _bestSetArr[0] ?? null,
216
- lastSessionExercise: lastData?.lastSessionExercise ?? null,
217
- lastSessionSets: lastData?.lastSessionSets
233
+ lastSessionExercise: lastRow?.lastSessionExercise ?? null,
234
+ lastSessionSets: lastRow?._lastSessionSetsArr,
235
+ lastAccumulationSessionExercise: lastAccumulationRow?.lastSessionExercise ?? null,
236
+ lastAccumulationSessionSets: lastAccumulationRow?._lastSessionSetsArr
218
237
  });
219
238
  });
220
239
  }
@@ -1 +1 @@
1
- {"version":3,"file":"WorkoutExerciseRepository.js","sourceRoot":"","sources":["../../../src/repositories/workout/WorkoutExerciseRepository.ts"],"names":[],"mappings":"AASA,OAAO,EACL,4BAA4B,EAC5B,uBAAuB,EACvB,kCAAkC,EAClC,iCAAiC,EACjC,wBAAwB,EACxB,sBAAsB,EACtB,8BAA8B,EAC9B,kBAAkB,EACnB,MAAM,0BAA0B,CAAC;AAGlC,OAAO,wBAAwB,MAAM,+CAA+C,CAAC;AACrF,OAAO,+BAA+B,MAAM,sCAAsC,CAAC;AACnF,OAAO,oCAAoC,MAAM,2CAA2C,CAAC;AAE7F;;GAEG;AACH,MAAM,CAAC,OAAO,OAAO,yBAA0B,SAAQ,+BAAgD;IAC7F,MAAM,CAAC,iBAAiB,CAA6B;IAE7D;QACE,KAAK,CAAC,uBAAuB,EAAE,IAAI,wBAAwB,EAAE,CAAC,CAAC;IACjE,CAAC;IAED,MAAM,CAAC,uBAAuB;QAC5B,MAAM,YAAY,GAAG,yBAAyB,CAAC,OAAO,EAAE,CAAC;QACzD,OAAO;YACL,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE;gBAChC,MAAM,YAAY,CAAC,gBAAgB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;YACpD,CAAC;YACD,UAAU,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE;gBAClC,MAAM,YAAY,CAAC,iBAAiB,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;YACtD,CAAC;SACF,CAAC;IACJ,CAAC;IAES,gBAAgB;QACxB,IAAI,CAAC,kBAAkB,CAAC,oCAAoC,CAAC,2BAA2B,EAAE,CAAC,CAAC;IAC9F,CAAC;IAEM,MAAM,CAAC,OAAO;QACnB,IAAI,CAAC,yBAAyB,CAAC,iBAAiB,EAAE,CAAC;YACjD,yBAAyB,CAAC,iBAAiB,GAAG,IAAI,yBAAyB,EAAE,CAAC;QAChF,CAAC;QACD,OAAO,yBAAyB,CAAC,iBAAiB,CAAC;IACrD,CAAC;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,wBAAwB,CAAC,MAAY;QAezC,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC;QAC9C,MAAM,QAAQ,GAAG,IAAI,CAAC,cAAc,CAAC;QAErC,wEAAwE;QACxE,MAAM,gBAAgB,GAAG,UAAU;aAChC,SAAS,CAAe;YACvB,yCAAyC;YACzC,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,uBAAuB,EAAE,MAAM,EAAE,EAAE;YACxD,gDAAgD;YAChD;gBACE,OAAO,EAAE;oBACP,IAAI,EAAE,QAAQ;oBACd,GAAG,EAAE,EAAE,OAAO,EAAE,yBAAyB,EAAE;oBAC3C,QAAQ,EAAE;wBACR;4BACE,MAAM,EAAE;gCACN,KAAK,EAAE,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE,WAAW,CAAC,EAAE;gCACrC,OAAO,EAAE,4BAA4B;6BACtC;yBACF;qBACF;oBACD,EAAE,EAAE,WAAW;iBAChB;aACF;YACD,qDAAqD;YACrD;gBACE,OAAO,EAAE;oBACP,IAAI,EAAE,QAAQ;oBACd,GAAG,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE;oBACrB,QAAQ,EAAE;wBACR;4BACE,MAAM,EAAE;gCACN,KAAK,EAAE,EAAE,GAAG,EAAE,CAAC,oBAAoB,EAAE,QAAQ,CAAC,EAAE;gCAChD,OAAO,EAAE,kCAAkC;6BAC5C;yBACF;wBACD;4BACE,UAAU,EAAE;gCACV,IAAI,EAAE,iCAAiC,CAAC,eAAe,CAAC,SAAS,EAAE,OAAO,CAAC;6BAC5E;yBACF;wBACD,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE;wBACvB,EAAE,MAAM,EAAE,CAAC,EAAE;wBACb,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE;qBAC1B;oBACD,EAAE,EAAE,aAAa;iBAClB;aACF;YACD,uDAAuD;YACvD;gBACE,OAAO,EAAE;oBACP,IAAI,EAAE,QAAQ;oBACd,GAAG,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE;oBACrB,QAAQ,EAAE;wBACR;4BACE,MAAM,EAAE;gCACN,KAAK,EAAE,EAAE,GAAG,EAAE,CAAC,oBAAoB,EAAE,QAAQ,CAAC,EAAE;gCAChD,OAAO,EAAE,kBAAkB;gCAC3B,YAAY,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE;gCAC3B,UAAU,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE;6BAC1B;yBACF;wBACD;4BACE,UAAU,EAAE;gCACV,IAAI,EAAE,iCAAiC,CAAC,eAAe,CACrD,eAAe,EACf,aAAa,CACd;6BACF;yBACF;wBACD,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE;wBACvB,EAAE,MAAM,EAAE,CAAC,EAAE;wBACb,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE;qBAC1B;oBACD,EAAE,EAAE,aAAa;iBAClB;aACF;SACF,CAAC;aACD,OAAO,EAAE,CAAC;QAEb,8DAA8D;QAC9D,MAAM,gBAAgB,GAAG,UAAU;aAChC,SAAS,CAAe;YACvB,kDAAkD;YAClD,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,sBAAsB,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE;YACvE,6BAA6B;YAC7B,EAAE,KAAK,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC,EAAE,EAAE;YAC5B,wCAAwC;YACxC;gBACE,OAAO,EAAE;oBACP,IAAI,EAAE,QAAQ;oBACd,GAAG,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE;oBACvB,QAAQ,EAAE;wBACR;4BACE,MAAM,EAAE;gCACN,KAAK,EAAE,EAAE,GAAG,EAAE,CAAC,mBAAmB,EAAE,UAAU,CAAC,EAAE;gCACjD,OAAO,EAAE,8BAA8B;6BACxC;yBACF;qBACF;oBACD,EAAE,EAAE,KAAK;iBACV;aACF;YACD,iEAAiE;YACjE,EAAE,OAAO,EAAE,MAAM,EAAE;YACnB,qDAAqD;YACrD;gBACE,OAAO,EAAE;oBACP,IAAI,EAAE,QAAQ;oBACd,GAAG,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE;oBACzB,QAAQ,EAAE;wBACR;4BACE,MAAM,EAAE;gCACN,KAAK,EAAE,EAAE,GAAG,EAAE,CAAC,2BAA2B,EAAE,QAAQ,CAAC,EAAE;gCACvD,OAAO,EAAE,kBAAkB;gCAC3B,UAAU,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE;6BAC1B;yBACF;wBACD,EAAE,MAAM,EAAE,CAAC,EAAE;qBACd;oBACD,EAAE,EAAE,YAAY;iBACjB;aACF;YACD,+DAA+D;YAC/D,EAAE,MAAM,EAAE,EAAE,cAAc,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE;YACjD,+EAA+E;YAC/E;gBACE,MAAM,EAAE;oBACN,GAAG,EAAE,wBAAwB;oBAC7B,mBAAmB,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE;iBACxC;aACF;YACD,qDAAqD;YACrD;gBACE,OAAO,EAAE;oBACP,IAAI,EAAE,QAAQ;oBACd,GAAG,EAAE,EAAE,MAAM,EAAE,+BAA+B,EAAE;oBAChD,QAAQ,EAAE;wBACR;4BACE,MAAM,EAAE;gCACN,KAAK,EAAE,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE,UAAU,CAAC,EAAE;gCACpC,OAAO,EAAE,kBAAkB;6BAC5B;yBACF;wBACD,8DAA8D;wBAC9D;4BACE,UAAU,EAAE;gCACV,QAAQ,EAAE,EAAE,aAAa,EAAE,CAAC,UAAU,EAAE,MAAM,CAAC,EAAE;6BAClD;yBACF;wBACD,EAAE,KAAK,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,EAAE;wBAC1B,EAAE,QAAQ,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,EAAE;qBAC9B;oBACD,EAAE,EAAE,qBAAqB;iBAC1B;aACF;SACF,CAAC;aACD,OAAO,EAAE,CAAC;QAEb,MAAM,CAAC,YAAY,EAAE,eAAe,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,gBAAgB,EAAE,gBAAgB,CAAC,CAAC,CAAC;QAEhG,mCAAmC;QACnC,MAAM,cAAc,GAAG,IAAI,GAAG,EAM3B,CAAC;QACJ,KAAK,MAAM,GAAG,IAAI,eAAe,EAAE,CAAC;YAClC,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE;gBAC1B,mBAAmB,EAAE,GAAG,CAAC,mBAAmB;gBAC5C,eAAe,EAAE,GAAG,CAAC,mBAAmB;aACzC,CAAC,CAAC;QACL,CAAC;QAED,6BAA6B;QAC7B,OAAO,YAAY,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;YAC9B,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,WAAW,EAAE,GAAG,cAAc,EAAE,GAAG,GAAG,CAAC;YACvE,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC3B,MAAM,IAAI,KAAK,CAAC,yCAAyC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC;YACtE,CAAC;YAED,MAAM,QAAQ,GAAG,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,GAAa,CAAC,CAAC;YAEvD,OAAO,wBAAwB,CAAC,KAAK,CAAC;gBACpC,GAAG,cAAc;gBACjB,aAAa,EAAE,SAAS,CAAC,CAAC,CAAC;gBAC3B,eAAe,EAAE,WAAW,CAAC,CAAC,CAAC,IAAI,IAAI;gBACvC,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,IAAI,IAAI;gBAC/B,mBAAmB,EAAE,QAAQ,EAAE,mBAAmB,IAAI,IAAI;gBAC1D,eAAe,EAAE,QAAQ,EAAE,eAAe;aAC3C,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;CACF"}
1
+ {"version":3,"file":"WorkoutExerciseRepository.js","sourceRoot":"","sources":["../../../src/repositories/workout/WorkoutExerciseRepository.ts"],"names":[],"mappings":"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"}
@@ -18,6 +18,7 @@ import {
18
18
  WorkoutSet_docType
19
19
  } from '@aneuhold/core-ts-db-lib';
20
20
  import type { UUID } from 'crypto';
21
+ import type { Document } from 'mongodb';
21
22
  import type { RepoListeners } from '../../services/RepoSubscriptionService.js';
22
23
  import WorkoutExerciseValidator from '../../validators/workout/ExerciseValidator.js';
23
24
  import WorkoutBaseWithUserIdRepository from './WorkoutBaseWithUserIdRepository.js';
@@ -58,9 +59,12 @@ export default class WorkoutExerciseRepository extends WorkoutBaseWithUserIdRepo
58
59
 
59
60
  /**
60
61
  * Builds {@link WorkoutExerciseCTO} objects for all exercises belonging to a user.
61
- * Uses two parallel MongoDB aggregation pipelines:
62
+ * Uses three parallel MongoDB aggregation pipelines:
62
63
  * - Pipeline A: exercise + equipmentType + bestCalibration + bestSet
63
- * - Pipeline B: lastSessionExercise + lastSessionSets (from most recent non-deload session)
64
+ * - Pipeline B (true latest): lastSessionExercise + lastSessionSets from the most
65
+ * recent completed session, regardless of cycle type / deload status.
66
+ * - Pipeline C (accumulation): lastAccumulationSessionExercise +
67
+ * lastAccumulationSessionSets from the most recent completed non-deload session.
64
68
  *
65
69
  * @param userId The user whose exercise CTOs to build.
66
70
  */
@@ -72,8 +76,8 @@ export default class WorkoutExerciseRepository extends WorkoutBaseWithUserIdRepo
72
76
  _bestSetArr: WorkoutSet[];
73
77
  }
74
78
 
75
- /** Raw shape of Pipeline B results. */
76
- interface PipelineBRow {
79
+ /** Raw shape of Pipeline B/C results. */
80
+ interface LastSessionRow {
77
81
  _id: string;
78
82
  lastSessionExercise: WorkoutSessionExercise;
79
83
  _lastSessionSetsArr: WorkoutSet[];
@@ -159,9 +163,36 @@ export default class WorkoutExerciseRepository extends WorkoutBaseWithUserIdRepo
159
163
  ])
160
164
  .toArray();
161
165
 
162
- // Pipeline B: lastSessionExercise + lastFirstSet per exercise
163
- const pipelineBPromise = collection
164
- .aggregate<PipelineBRow>([
166
+ // Builds the pipeline that finds, per exercise, the most recent session
167
+ // exercise from a completed session — optionally restricted to non-deload
168
+ // sessions (where plannedRir is not null on at least one set).
169
+ const buildLastSessionPipeline = (accumulationOnly: boolean): Document[] => {
170
+ const accumulationFilter: Document[] = accumulationOnly
171
+ ? [
172
+ // Check for non-deload sets (plannedRir is not null)
173
+ {
174
+ $lookup: {
175
+ from: collName,
176
+ let: { seId: '$_se._id' },
177
+ pipeline: [
178
+ {
179
+ $match: {
180
+ $expr: { $eq: ['$workoutSessionExerciseId', '$$seId'] },
181
+ docType: WorkoutSet_docType,
182
+ plannedRir: { $ne: null }
183
+ }
184
+ },
185
+ { $limit: 1 }
186
+ ],
187
+ as: '_nonDeload'
188
+ }
189
+ },
190
+ // Keep only session exercises with at least one non-deload set
191
+ { $match: { '_nonDeload.0': { $exists: true } } }
192
+ ]
193
+ : [];
194
+
195
+ return [
165
196
  // Start with all completed sessions for this user
166
197
  { $match: { docType: WorkoutSession_docType, userId, complete: true } },
167
198
  // Most recent sessions first
@@ -184,26 +215,7 @@ export default class WorkoutExerciseRepository extends WorkoutBaseWithUserIdRepo
184
215
  },
185
216
  // One row per session exercise (unwind coalescence optimization)
186
217
  { $unwind: '$_se' },
187
- // Check for non-deload sets (plannedRir is not null)
188
- {
189
- $lookup: {
190
- from: collName,
191
- let: { seId: '$_se._id' },
192
- pipeline: [
193
- {
194
- $match: {
195
- $expr: { $eq: ['$workoutSessionExerciseId', '$$seId'] },
196
- docType: WorkoutSet_docType,
197
- plannedRir: { $ne: null }
198
- }
199
- },
200
- { $limit: 1 }
201
- ],
202
- as: '_nonDeload'
203
- }
204
- },
205
- // Keep only session exercises with at least one non-deload set
206
- { $match: { '_nonDeload.0': { $exists: true } } },
218
+ ...accumulationFilter,
207
219
  // Per exercise, keep only the most recent session exercise ($first after sort)
208
220
  {
209
221
  $group: {
@@ -235,25 +247,27 @@ export default class WorkoutExerciseRepository extends WorkoutBaseWithUserIdRepo
235
247
  as: '_lastSessionSetsArr'
236
248
  }
237
249
  }
238
- ])
250
+ ];
251
+ };
252
+
253
+ // Pipeline B: most recent session exercise per exercise (any cycle type)
254
+ const pipelineBPromise = collection
255
+ .aggregate<LastSessionRow>(buildLastSessionPipeline(false))
239
256
  .toArray();
240
257
 
241
- const [rawExercises, rawLastSessions] = await Promise.all([pipelineAPromise, pipelineBPromise]);
258
+ // Pipeline C: most recent accumulation (non-deload) session exercise per exercise
259
+ const pipelineCPromise = collection
260
+ .aggregate<LastSessionRow>(buildLastSessionPipeline(true))
261
+ .toArray();
242
262
 
243
- // Build lookup map from Pipeline B
244
- const lastSessionMap = new Map<
245
- string,
246
- {
247
- lastSessionExercise: WorkoutSessionExercise;
248
- lastSessionSets: WorkoutSet[];
249
- }
250
- >();
251
- for (const row of rawLastSessions) {
252
- lastSessionMap.set(row._id, {
253
- lastSessionExercise: row.lastSessionExercise,
254
- lastSessionSets: row._lastSessionSetsArr
255
- });
256
- }
263
+ const [rawExercises, rawLastSessions, rawLastAccumulationSessions] = await Promise.all([
264
+ pipelineAPromise,
265
+ pipelineBPromise,
266
+ pipelineCPromise
267
+ ]);
268
+
269
+ const lastSessionMap = new Map(rawLastSessions.map((row) => [row._id, row]));
270
+ const lastAccumulationMap = new Map(rawLastAccumulationSessions.map((row) => [row._id, row]));
257
271
 
258
272
  // Assemble and validate CTOs
259
273
  return rawExercises.map((raw) => {
@@ -262,15 +276,18 @@ export default class WorkoutExerciseRepository extends WorkoutBaseWithUserIdRepo
262
276
  throw new Error(`Equipment type not found for exercise ${raw._id}`);
263
277
  }
264
278
 
265
- const lastData = lastSessionMap.get(raw._id as string);
279
+ const lastRow = lastSessionMap.get(raw._id as string);
280
+ const lastAccumulationRow = lastAccumulationMap.get(raw._id as string);
266
281
 
267
282
  return WorkoutExerciseCTOSchema.parse({
268
283
  ...exerciseFields,
269
284
  equipmentType: _equipArr[0],
270
285
  bestCalibration: _bestCalArr[0] ?? null,
271
286
  bestSet: _bestSetArr[0] ?? null,
272
- lastSessionExercise: lastData?.lastSessionExercise ?? null,
273
- lastSessionSets: lastData?.lastSessionSets
287
+ lastSessionExercise: lastRow?.lastSessionExercise ?? null,
288
+ lastSessionSets: lastRow?._lastSessionSetsArr,
289
+ lastAccumulationSessionExercise: lastAccumulationRow?.lastSessionExercise ?? null,
290
+ lastAccumulationSessionSets: lastAccumulationRow?._lastSessionSetsArr
274
291
  });
275
292
  });
276
293
  }
@@ -16,5 +16,10 @@ export default class MigrationService {
16
16
  * @param dryRun Whether or not to actually make the changes or just log them.
17
17
  */
18
18
  static migrateDb(dryRun?: boolean): Promise<void>;
19
+ /**
20
+ * Migrates dashboard user configs that are missing enableAdminPage
21
+ * or the adminPage feature flag.
22
+ */
23
+ private static migrateUserConfigs;
19
24
  }
20
25
  //# sourceMappingURL=MigrationService.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"MigrationService.d.ts","sourceRoot":"","sources":["../../src/services/MigrationService.ts"],"names":[],"mappings":"AAeA;;;;;;GAMG;AACH,MAAM,CAAC,OAAO,OAAO,gBAAgB;IACnC;;;;;;;;OAQG;WACU,SAAS,CAAC,MAAM,UAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;CAkDtD"}
1
+ {"version":3,"file":"MigrationService.d.ts","sourceRoot":"","sources":["../../src/services/MigrationService.ts"],"names":[],"mappings":"AAoBA;;;;;;GAMG;AACH,MAAM,CAAC,OAAO,OAAO,gBAAgB;IACnC;;;;;;;;OAQG;WACU,SAAS,CAAC,MAAM,UAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAQrD;;;OAGG;mBACkB,kBAAkB;CAwExC"}
@@ -2,14 +2,19 @@
2
2
  // @ts-nocheck
3
3
  import { DR } from '@aneuhold/core-ts-lib';
4
4
  import UserRepository from '../repositories/common/UserRepository.js';
5
+ import DashboardUserConfigRepository from '../repositories/dashboard/DashboardUserConfigRepository.js';
5
6
  /**
6
- * The default values for each project access field. New fields should be
7
- * added here and defaulted to `false` so existing users don't gain access
8
- * automatically.
7
+ * The default values for enabledFeatures. New feature flags should be
8
+ * added here and defaulted to `false`.
9
9
  */
10
- const PROJECT_ACCESS_DEFAULTS = {
11
- dashboard: false,
12
- workout: false
10
+ const ENABLED_FEATURES_DEFAULTS = {
11
+ financePage: false,
12
+ automationPage: false,
13
+ entertainmentPage: false,
14
+ homePageLinks: false,
15
+ useConfettiForTasks: false,
16
+ catImageOnHomePage: false,
17
+ adminPage: false
13
18
  };
14
19
  /**
15
20
  * A service for migrating the DB to a new state after an existing document
@@ -29,40 +34,63 @@ export default class MigrationService {
29
34
  * @param dryRun Whether or not to actually make the changes or just log them.
30
35
  */
31
36
  static async migrateDb(dryRun = false) {
32
- DR.logger.info('Starting migration...');
37
+ DR.logger.info(`Starting migration... (dryRun=${dryRun})`);
38
+ await this.migrateUserConfigs(dryRun);
39
+ DR.logger.success('Migration complete.');
40
+ }
41
+ /**
42
+ * Migrates dashboard user configs that are missing enableAdminPage
43
+ * or the adminPage feature flag.
44
+ */
45
+ static async migrateUserConfigs(dryRun) {
46
+ const configRepo = DashboardUserConfigRepository.getRepo();
47
+ const allConfigs = await configRepo.getAll();
48
+ // Build a userId -> userName lookup
33
49
  const userRepo = UserRepository.getRepo();
34
- const users = await userRepo.getAll();
35
- // Find users that are missing projectAccess entirely or are missing
36
- // any of the expected fields.
37
- const usersToUpdate = users.filter((user) => {
38
- if (!user.projectAccess)
39
- return true;
40
- return Object.keys(PROJECT_ACCESS_DEFAULTS).some((key) => user.projectAccess[key] === undefined);
41
- });
42
- DR.logger.info(`Found ${usersToUpdate.length} of ${users.length} users with missing projectAccess fields.`);
43
- if (usersToUpdate.length === 0) {
44
- DR.logger.success('No migration needed.');
50
+ const allUsers = await userRepo.getAll();
51
+ const userNameMap = new Map(allUsers.map((u) => [u._id, u.userName]));
52
+ // Build all updates in memory first, tracking only changed fields
53
+ const updates = [];
54
+ for (const config of allConfigs) {
55
+ const changes = [];
56
+ const updateDoc = { _id: config._id };
57
+ if (config.enableAdminPage === undefined) {
58
+ changes.push('enableAdminPage: undefined false');
59
+ updateDoc.enableAdminPage = false;
60
+ }
61
+ const existingFeatures = config.enabledFeatures ?? {};
62
+ if (!existingFeatures || existingFeatures.adminPage === undefined) {
63
+ changes.push('enabledFeatures.adminPage: undefined → false');
64
+ updateDoc.enabledFeatures = { ...ENABLED_FEATURES_DEFAULTS, ...existingFeatures };
65
+ }
66
+ if (changes.length === 0) {
67
+ continue;
68
+ }
69
+ const userName = userNameMap.get(config.userId) ?? 'unknown';
70
+ updates.push({ configId: config._id, userName, changes, updateDoc });
71
+ }
72
+ DR.logger.info(`Found ${updates.length} of ${allConfigs.length} user configs needing admin page fields.`);
73
+ if (updates.length === 0) {
74
+ DR.logger.success('No user config migration needed.');
45
75
  return;
46
76
  }
77
+ // Log exactly what will change
78
+ for (const update of updates) {
79
+ DR.logger.info(` ${update.userName} (${update.configId}):`);
80
+ for (const change of update.changes) {
81
+ DR.logger.info(` ${change}`);
82
+ }
83
+ }
47
84
  if (dryRun) {
48
- DR.logger.info('Dry run: Would update the following users:');
49
- usersToUpdate.forEach((user) => {
50
- const existing = user.projectAccess ?? {};
51
- const merged = { ...PROJECT_ACCESS_DEFAULTS, ...existing };
52
- DR.logger.info(` - ${user.userName} (${user._id}): ${JSON.stringify(existing)} → ${JSON.stringify(merged)}`);
53
- });
85
+ DR.logger.info(`Dry run complete. ${updates.length} configs would be updated.`);
54
86
  return;
55
87
  }
56
- for (const user of usersToUpdate) {
57
- const existing = user.projectAccess ?? {};
58
- const merged = { ...PROJECT_ACCESS_DEFAULTS, ...existing };
59
- await userRepo.update({
60
- _id: user._id,
61
- projectAccess: merged
62
- });
63
- DR.logger.info(`Updated user ${user.userName} (${user._id}): ${JSON.stringify(existing)} → ${JSON.stringify(merged)}`);
88
+ // Execute updates
89
+ for (const update of updates) {
90
+ await configRepo.update(update.updateDoc);
91
+ DR.logger.info(`Updated config for ${update.userName} (${update.configId})`);
64
92
  }
65
- DR.logger.success(`Migration complete. Updated ${usersToUpdate.length} users.`);
93
+ DR.logger.success(`User config migration complete. Updated ${updates.length} configs.`);
66
94
  }
67
95
  }
68
96
  //# sourceMappingURL=MigrationService.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"MigrationService.js","sourceRoot":"","sources":["../../src/services/MigrationService.ts"],"names":[],"mappings":"AAAA,oBAAoB;AACpB,cAAc;AACd,OAAO,EAAE,EAAE,EAAE,MAAM,uBAAuB,CAAC;AAC3C,OAAO,cAAc,MAAM,0CAA0C,CAAC;AAEtE;;;;GAIG;AACH,MAAM,uBAAuB,GAAG;IAC9B,SAAS,EAAE,KAAK;IAChB,OAAO,EAAE,KAAK;CACf,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,CAAC,OAAO,OAAO,gBAAgB;IACnC;;;;;;;;OAQG;IACH,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,GAAG,KAAK;QACnC,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;QAExC,MAAM,QAAQ,GAAG,cAAc,CAAC,OAAO,EAAE,CAAC;QAC1C,MAAM,KAAK,GAAG,MAAM,QAAQ,CAAC,MAAM,EAAE,CAAC;QAEtC,oEAAoE;QACpE,8BAA8B;QAC9B,MAAM,aAAa,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE;YAC1C,IAAI,CAAC,IAAI,CAAC,aAAa;gBAAE,OAAO,IAAI,CAAC;YACrC,OAAO,MAAM,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC,IAAI,CAC9C,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,KAAK,SAAS,CAC/C,CAAC;QACJ,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,MAAM,CAAC,IAAI,CACZ,SAAS,aAAa,CAAC,MAAM,OAAO,KAAK,CAAC,MAAM,2CAA2C,CAC5F,CAAC;QAEF,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC/B,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,sBAAsB,CAAC,CAAC;YAC1C,OAAO;QACT,CAAC;QAED,IAAI,MAAM,EAAE,CAAC;YACX,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,4CAA4C,CAAC,CAAC;YAC7D,aAAa,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE;gBAC7B,MAAM,QAAQ,GAAG,IAAI,CAAC,aAAa,IAAI,EAAE,CAAC;gBAC1C,MAAM,MAAM,GAAG,EAAE,GAAG,uBAAuB,EAAE,GAAG,QAAQ,EAAE,CAAC;gBAC3D,EAAE,CAAC,MAAM,CAAC,IAAI,CACZ,OAAO,IAAI,CAAC,QAAQ,KAAK,IAAI,CAAC,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,MAAM,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAC9F,CAAC;YACJ,CAAC,CAAC,CAAC;YACH,OAAO;QACT,CAAC;QAED,KAAK,MAAM,IAAI,IAAI,aAAa,EAAE,CAAC;YACjC,MAAM,QAAQ,GAAG,IAAI,CAAC,aAAa,IAAI,EAAE,CAAC;YAC1C,MAAM,MAAM,GAAG,EAAE,GAAG,uBAAuB,EAAE,GAAG,QAAQ,EAAE,CAAC;YAC3D,MAAM,QAAQ,CAAC,MAAM,CAAC;gBACpB,GAAG,EAAE,IAAI,CAAC,GAAG;gBACb,aAAa,EAAE,MAAM;aACtB,CAAC,CAAC;YACH,EAAE,CAAC,MAAM,CAAC,IAAI,CACZ,gBAAgB,IAAI,CAAC,QAAQ,KAAK,IAAI,CAAC,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,MAAM,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CACvG,CAAC;QACJ,CAAC;QAED,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,+BAA+B,aAAa,CAAC,MAAM,SAAS,CAAC,CAAC;IAClF,CAAC;CACF"}
1
+ {"version":3,"file":"MigrationService.js","sourceRoot":"","sources":["../../src/services/MigrationService.ts"],"names":[],"mappings":"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"}
@@ -2,15 +2,20 @@
2
2
  // @ts-nocheck
3
3
  import { DR } from '@aneuhold/core-ts-lib';
4
4
  import UserRepository from '../repositories/common/UserRepository.js';
5
+ import DashboardUserConfigRepository from '../repositories/dashboard/DashboardUserConfigRepository.js';
5
6
 
6
7
  /**
7
- * The default values for each project access field. New fields should be
8
- * added here and defaulted to `false` so existing users don't gain access
9
- * automatically.
8
+ * The default values for enabledFeatures. New feature flags should be
9
+ * added here and defaulted to `false`.
10
10
  */
11
- const PROJECT_ACCESS_DEFAULTS = {
12
- dashboard: false,
13
- workout: false
11
+ const ENABLED_FEATURES_DEFAULTS = {
12
+ financePage: false,
13
+ automationPage: false,
14
+ entertainmentPage: false,
15
+ homePageLinks: false,
16
+ useConfettiForTasks: false,
17
+ catImageOnHomePage: false,
18
+ adminPage: false
14
19
  };
15
20
 
16
21
  /**
@@ -31,53 +36,87 @@ export default class MigrationService {
31
36
  * @param dryRun Whether or not to actually make the changes or just log them.
32
37
  */
33
38
  static async migrateDb(dryRun = false): Promise<void> {
34
- DR.logger.info('Starting migration...');
39
+ DR.logger.info(`Starting migration... (dryRun=${dryRun})`);
35
40
 
41
+ await this.migrateUserConfigs(dryRun);
42
+
43
+ DR.logger.success('Migration complete.');
44
+ }
45
+
46
+ /**
47
+ * Migrates dashboard user configs that are missing enableAdminPage
48
+ * or the adminPage feature flag.
49
+ */
50
+ private static async migrateUserConfigs(dryRun: boolean): Promise<void> {
51
+ const configRepo = DashboardUserConfigRepository.getRepo();
52
+ const allConfigs = await configRepo.getAll();
53
+
54
+ // Build a userId -> userName lookup
36
55
  const userRepo = UserRepository.getRepo();
37
- const users = await userRepo.getAll();
56
+ const allUsers = await userRepo.getAll();
57
+ const userNameMap = new Map(allUsers.map((u) => [u._id, u.userName]));
58
+
59
+ // Build all updates in memory first, tracking only changed fields
60
+ const updates: Array<{
61
+ configId: string;
62
+ userName: string;
63
+ changes: string[];
64
+ updateDoc: Record<string, unknown>;
65
+ }> = [];
66
+
67
+ for (const config of allConfigs) {
68
+ const changes: string[] = [];
69
+ const updateDoc: Record<string, unknown> = { _id: config._id };
38
70
 
39
- // Find users that are missing projectAccess entirely or are missing
40
- // any of the expected fields.
41
- const usersToUpdate = users.filter((user) => {
42
- if (!user.projectAccess) return true;
43
- return Object.keys(PROJECT_ACCESS_DEFAULTS).some(
44
- (key) => user.projectAccess[key] === undefined
45
- );
46
- });
71
+ if (config.enableAdminPage === undefined) {
72
+ changes.push('enableAdminPage: undefined false');
73
+ updateDoc.enableAdminPage = false;
74
+ }
75
+
76
+ const existingFeatures = config.enabledFeatures ?? {};
77
+ if (!existingFeatures || existingFeatures.adminPage === undefined) {
78
+ changes.push('enabledFeatures.adminPage: undefined → false');
79
+ updateDoc.enabledFeatures = { ...ENABLED_FEATURES_DEFAULTS, ...existingFeatures };
80
+ }
81
+
82
+ if (changes.length === 0) {
83
+ continue;
84
+ }
85
+
86
+ const userName = userNameMap.get(config.userId) ?? 'unknown';
87
+ updates.push({ configId: config._id, userName, changes, updateDoc });
88
+ }
47
89
 
48
90
  DR.logger.info(
49
- `Found ${usersToUpdate.length} of ${users.length} users with missing projectAccess fields.`
91
+ `Found ${updates.length} of ${allConfigs.length} user configs needing admin page fields.`
50
92
  );
51
93
 
52
- if (usersToUpdate.length === 0) {
53
- DR.logger.success('No migration needed.');
94
+ if (updates.length === 0) {
95
+ DR.logger.success('No user config migration needed.');
54
96
  return;
55
97
  }
56
98
 
99
+ // Log exactly what will change
100
+ for (const update of updates) {
101
+ DR.logger.info(` ${update.userName} (${update.configId}):`);
102
+ for (const change of update.changes) {
103
+ DR.logger.info(` ${change}`);
104
+ }
105
+ }
106
+
57
107
  if (dryRun) {
58
- DR.logger.info('Dry run: Would update the following users:');
59
- usersToUpdate.forEach((user) => {
60
- const existing = user.projectAccess ?? {};
61
- const merged = { ...PROJECT_ACCESS_DEFAULTS, ...existing };
62
- DR.logger.info(
63
- ` - ${user.userName} (${user._id}): ${JSON.stringify(existing)} → ${JSON.stringify(merged)}`
64
- );
65
- });
108
+ DR.logger.info(`Dry run complete. ${updates.length} configs would be updated.`);
66
109
  return;
67
110
  }
68
111
 
69
- for (const user of usersToUpdate) {
70
- const existing = user.projectAccess ?? {};
71
- const merged = { ...PROJECT_ACCESS_DEFAULTS, ...existing };
72
- await userRepo.update({
73
- _id: user._id,
74
- projectAccess: merged
75
- });
76
- DR.logger.info(
77
- `Updated user ${user.userName} (${user._id}): ${JSON.stringify(existing)} → ${JSON.stringify(merged)}`
78
- );
112
+ // Execute updates
113
+ for (const update of updates) {
114
+ await configRepo.update(update.updateDoc);
115
+ DR.logger.info(`Updated config for ${update.userName} (${update.configId})`);
79
116
  }
80
117
 
81
- DR.logger.success(`Migration complete. Updated ${usersToUpdate.length} users.`);
118
+ DR.logger.success(
119
+ `User config migration complete. Updated ${updates.length} configs.`
120
+ );
82
121
  }
83
122
  }
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@aneuhold/be-ts-db-lib",
3
3
  "author": "Anton G. Neuhold Jr.",
4
4
  "license": "MIT",
5
- "version": "4.2.21",
5
+ "version": "4.2.23",
6
6
  "description": "A backend database library meant to actually interact with various databases in personal projects",
7
7
  "packageManager": "pnpm@10.25.0",
8
8
  "type": "module",
@@ -58,8 +58,8 @@
58
58
  "MongoDB"
59
59
  ],
60
60
  "dependencies": {
61
- "@aneuhold/be-ts-lib": "^3.1.8",
62
- "@aneuhold/core-ts-db-lib": "^5.0.3",
61
+ "@aneuhold/be-ts-lib": "^3.1.10",
62
+ "@aneuhold/core-ts-db-lib": "^5.0.5",
63
63
  "@aneuhold/core-ts-lib": "^2.4.3",
64
64
  "bson": "^7.0.0",
65
65
  "google-auth-library": "^10.6.1",