Site Maintainer commited on
Commit
8776c0f
·
1 Parent(s): 8e21ae0

Ordering support: position field, append new items, admin position endpoints, sort by position+createdAt

Browse files
Files changed (2) hide show
  1. models/SourceText.js +2 -0
  2. routes/auth.js +58 -2
models/SourceText.js CHANGED
@@ -32,6 +32,8 @@ const sourceTextSchema = new mongoose.Schema({
32
  imageSize: { type: Number, default: 200 },
33
  // Add 'portrait-split' for week 4–6 advanced layout (non-breaking, additive)
34
  imageAlignment: { type: String, enum: ['left', 'center', 'right', 'portrait-split'], default: 'center' },
 
 
35
  culturalElements: [culturalElementSchema],
36
  difficulty: {
37
  type: String,
 
32
  imageSize: { type: Number, default: 200 },
33
  // Add 'portrait-split' for week 4–6 advanced layout (non-breaking, additive)
34
  imageAlignment: { type: String, enum: ['left', 'center', 'right', 'portrait-split'], default: 'center' },
35
+ // Optional display ordering within a week/category (ascending). If absent, fallback to createdAt.
36
+ position: { type: Number },
37
  culturalElements: [culturalElementSchema],
38
  difficulty: {
39
  type: String,
routes/auth.js CHANGED
@@ -160,7 +160,7 @@ router.delete('/admin/practice-examples/:id', authenticateToken, async (req, res
160
  router.get('/admin/tutorial-tasks', authenticateToken, async (req, res) => {
161
  try {
162
  const SourceText = require('../models/SourceText');
163
- const tutorialTasks = await SourceText.find({ category: 'tutorial' }).sort({ weekNumber: 1, createdAt: -1 });
164
  res.json({ success: true, tutorialTasks });
165
  } catch (error) {
166
  console.error('Get tutorial tasks error:', error);
@@ -180,6 +180,19 @@ router.post('/admin/tutorial-tasks', authenticateToken, requireAdmin, async (req
180
  if (!content || content.trim() === '') return res.status(400).json({ error: 'Content is required' });
181
  }
182
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
  const newTask = new SourceText({
184
  content: content || (imageUrl ? 'Image-based task' : ''),
185
  weekNumber: parseInt(weekNumber),
@@ -189,6 +202,7 @@ router.post('/admin/tutorial-tasks', authenticateToken, requireAdmin, async (req
189
  sourceType: 'tutorial',
190
  imageUrl,
191
  imageAlt,
 
192
  ...(imageUrl && (!content || content.trim() === '') && { imageSize: imageSize || 200 }),
193
  ...(imageUrl && (!content || content.trim() === '') && { imageAlignment: imageAlignment || 'center' }),
194
  translationBrief
@@ -202,6 +216,20 @@ router.post('/admin/tutorial-tasks', authenticateToken, requireAdmin, async (req
202
  }
203
  });
204
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
205
  router.put('/admin/tutorial-tasks/:id', authenticateToken, requireAdmin, async (req, res) => {
206
  try {
207
  const SourceText = require('../models/SourceText');
@@ -236,7 +264,7 @@ router.delete('/admin/tutorial-tasks/:id', authenticateToken, requireAdmin, asyn
236
  router.get('/admin/weekly-practice', authenticateToken, async (req, res) => {
237
  try {
238
  const SourceText = require('../models/SourceText');
239
- const weeklyPractice = await SourceText.find({ category: 'weekly-practice' }).sort({ weekNumber: 1, createdAt: -1 });
240
  res.json({ success: true, weeklyPractice });
241
  } catch (error) {
242
  console.error('Get weekly practice error:', error);
@@ -256,6 +284,19 @@ router.post('/admin/weekly-practice', authenticateToken, requireAdmin, async (re
256
  if (!content || content.trim() === '') return res.status(400).json({ error: 'Content is required' });
257
  }
258
 
 
 
 
 
 
 
 
 
 
 
 
 
 
259
  const newPractice = new SourceText({
260
  content: content || (imageUrl ? 'Image-based practice' : ''),
261
  weekNumber: parseInt(weekNumber),
@@ -265,6 +306,7 @@ router.post('/admin/weekly-practice', authenticateToken, requireAdmin, async (re
265
  sourceType: 'weekly-practice',
266
  imageUrl,
267
  imageAlt,
 
268
  ...(imageUrl && (!content || content.trim() === '') && { imageSize: imageSize || 200 }),
269
  ...(imageUrl && (!content || content.trim() === '') && { imageAlignment: imageAlignment || 'center' }),
270
  translationBrief
@@ -278,6 +320,20 @@ router.post('/admin/weekly-practice', authenticateToken, requireAdmin, async (re
278
  }
279
  });
280
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
281
  router.put('/admin/weekly-practice/:id', authenticateToken, requireAdmin, async (req, res) => {
282
  try {
283
  const SourceText = require('../models/SourceText');
 
160
  router.get('/admin/tutorial-tasks', authenticateToken, async (req, res) => {
161
  try {
162
  const SourceText = require('../models/SourceText');
163
+ const tutorialTasks = await SourceText.find({ category: 'tutorial' }).sort({ weekNumber: 1, position: 1, createdAt: 1 });
164
  res.json({ success: true, tutorialTasks });
165
  } catch (error) {
166
  console.error('Get tutorial tasks error:', error);
 
180
  if (!content || content.trim() === '') return res.status(400).json({ error: 'Content is required' });
181
  }
182
 
183
+ // Determine optional position for weeks 4-6 so newly added items append to the end
184
+ let position;
185
+ if (parseInt(weekNumber) >= 4) {
186
+ try {
187
+ const last = await SourceText.find({ category: 'tutorial', weekNumber: parseInt(weekNumber) })
188
+ .sort({ position: -1 })
189
+ .limit(1);
190
+ position = ((last[0] && typeof last[0].position === 'number') ? last[0].position : 0) + 1;
191
+ } catch (e) {
192
+ position = undefined;
193
+ }
194
+ }
195
+
196
  const newTask = new SourceText({
197
  content: content || (imageUrl ? 'Image-based task' : ''),
198
  weekNumber: parseInt(weekNumber),
 
202
  sourceType: 'tutorial',
203
  imageUrl,
204
  imageAlt,
205
+ ...(position !== undefined ? { position } : {}),
206
  ...(imageUrl && (!content || content.trim() === '') && { imageSize: imageSize || 200 }),
207
  ...(imageUrl && (!content || content.trim() === '') && { imageAlignment: imageAlignment || 'center' }),
208
  translationBrief
 
216
  }
217
  });
218
 
219
+ // Update position for a tutorial task (admin)
220
+ router.put('/admin/tutorial-tasks/:id/position', authenticateToken, requireAdmin, async (req, res) => {
221
+ try {
222
+ const SourceText = require('../models/SourceText');
223
+ const { position } = req.body;
224
+ const updated = await SourceText.findByIdAndUpdate(req.params.id, { position }, { new: true, runValidators: true });
225
+ if (!updated) return res.status(404).json({ error: 'Tutorial task not found' });
226
+ res.json({ success: true, message: 'Position updated', task: updated });
227
+ } catch (error) {
228
+ console.error('Update tutorial task position error:', error);
229
+ res.status(500).json({ error: 'Failed to update tutorial task position' });
230
+ }
231
+ });
232
+
233
  router.put('/admin/tutorial-tasks/:id', authenticateToken, requireAdmin, async (req, res) => {
234
  try {
235
  const SourceText = require('../models/SourceText');
 
264
  router.get('/admin/weekly-practice', authenticateToken, async (req, res) => {
265
  try {
266
  const SourceText = require('../models/SourceText');
267
+ const weeklyPractice = await SourceText.find({ category: 'weekly-practice' }).sort({ weekNumber: 1, position: 1, createdAt: 1 });
268
  res.json({ success: true, weeklyPractice });
269
  } catch (error) {
270
  console.error('Get weekly practice error:', error);
 
284
  if (!content || content.trim() === '') return res.status(400).json({ error: 'Content is required' });
285
  }
286
 
287
+ // Determine optional position for weeks 4-6 so newly added items append to the end
288
+ let position;
289
+ if (parseInt(weekNumber) >= 4) {
290
+ try {
291
+ const last = await SourceText.find({ category: 'weekly-practice', weekNumber: parseInt(weekNumber) })
292
+ .sort({ position: -1 })
293
+ .limit(1);
294
+ position = ((last[0] && typeof last[0].position === 'number') ? last[0].position : 0) + 1;
295
+ } catch (e) {
296
+ position = undefined;
297
+ }
298
+ }
299
+
300
  const newPractice = new SourceText({
301
  content: content || (imageUrl ? 'Image-based practice' : ''),
302
  weekNumber: parseInt(weekNumber),
 
306
  sourceType: 'weekly-practice',
307
  imageUrl,
308
  imageAlt,
309
+ ...(position !== undefined ? { position } : {}),
310
  ...(imageUrl && (!content || content.trim() === '') && { imageSize: imageSize || 200 }),
311
  ...(imageUrl && (!content || content.trim() === '') && { imageAlignment: imageAlignment || 'center' }),
312
  translationBrief
 
320
  }
321
  });
322
 
323
+ // Update position for a weekly practice task (admin)
324
+ router.put('/admin/weekly-practice/:id/position', authenticateToken, requireAdmin, async (req, res) => {
325
+ try {
326
+ const SourceText = require('../models/SourceText');
327
+ const { position } = req.body;
328
+ const updated = await SourceText.findByIdAndUpdate(req.params.id, { position }, { new: true, runValidators: true });
329
+ if (!updated) return res.status(404).json({ error: 'Weekly practice not found' });
330
+ res.json({ success: true, message: 'Position updated', practice: updated });
331
+ } catch (error) {
332
+ console.error('Update weekly practice position error:', error);
333
+ res.status(500).json({ error: 'Failed to update weekly practice position' });
334
+ }
335
+ });
336
+
337
  router.put('/admin/weekly-practice/:id', authenticateToken, requireAdmin, async (req, res) => {
338
  try {
339
  const SourceText = require('../models/SourceText');