All files / src/routes fun-facts.js

85.36% Statements 70/82
91.48% Branches 43/47
100% Functions 5/5
88.4% Lines 61/69

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143                    2x 2x 2x 2x 2x 2x     2x 5x 5x     3x 1x 1x     3x 3x   2x 2x         2x 3x 3x   2x   2x               2x 5x 5x   4x 4x 4x   3x 3x   3x                     3x               2x 5x 5x   4x 4x   4x 4x 4x   3x 3x 3x 3x 3x 1x   3x 2x     3x 1x     2x 2x   2x                     2x 3x 3x   2x 2x 2x   1x   1x                   2x  
/**
 * Fun facts routes — CRUD for language/culture facts shown on splash screen.
 *
 * GET    /api/fun-facts             -> All active facts (any authenticated user)
 * GET    /api/admin/fun-facts       -> All facts (admin)
 * POST   /api/admin/fun-facts       -> Create fact (admin)
 * PUT    /api/admin/fun-facts/:id   -> Update fact (admin)
 * DELETE /api/admin/fun-facts/:id   -> Delete fact (admin)
 */
 
const router = require('express').Router();
const { db } = require('../utils/firebase');
const { generateId, now } = require('../utils/helpers');
const { requireAdmin } = require('../middleware/auth');
const { queryDocs } = require('../utils/firestore-helpers');
const log = require('../utils/log');
 
// -- All active facts (any authenticated user) --
router.get('/fun-facts', async (req, res) => {
  try {
    const results = await queryDocs(db.collection('funFacts').where('isActive', '==', true));
 
    // Shuffle (Firestore has no RANDOM() order)
    for (let i = results.length - 1; i > 0; i--) {
      const j = Math.floor(Math.random() * (i + 1));
      [results[i], results[j]] = [results[j], results[i]];
    }
 
    res.set('Cache-Control', 'public, max-age=3600');
    return res.json(results);
  } catch (err) {
    log.error('fun-facts', 'Failed to fetch active fun facts', { error: err.message });
    return res.status(500).json({ error: 'Internal server error' });
  }
});
 
// -- All facts (admin) --
router.get('/admin/fun-facts', async (req, res) => {
  try {
    if (requireAdmin(req, res)) return;
 
    const results = await queryDocs(db.collection('funFacts').orderBy('createdAt', 'desc'));
 
    return res.json(results);
  } catch (err) {
    log.error('fun-facts', 'Failed to fetch all fun facts', { error: err.message });
    return res.status(500).json({ error: 'Internal server error' });
  }
});
 
// -- Create fact (admin) --
router.post('/admin/fun-facts', async (req, res) => {
  try {
    if (requireAdmin(req, res)) return;
 
    const body = req.body;
    Iif (!body) return res.status(400).json({ error: 'Invalid JSON body' });
    if (!body.text) return res.status(400).json({ error: 'text is required' });
 
    const id = generateId();
    const timestamp = now();
 
    await db.doc(`funFacts/${id}`).set({
      id,
      text: body.text,
      category: body.category || 'trivia',
      emoji: body.emoji || '',
      sourceLanguage: body.sourceLanguage || body.source_language || '',
      isActive: body.isActive === undefined ? body.is_active !== false : !!body.isActive,
      createdAt: timestamp,
      updatedAt: timestamp,
    });
 
    return res.json({ success: true, id });
  } catch (err) {
    log.error('fun-facts', 'Failed to create fun fact', { error: err.message });
    return res.status(500).json({ error: 'Internal server error' });
  }
});
 
// -- Update fact (admin) --
router.put('/admin/fun-facts/:id', async (req, res) => {
  try {
    if (requireAdmin(req, res)) return;
 
    const body = req.body;
    Iif (!body) return res.status(400).json({ error: 'Invalid JSON body' });
 
    const docRef = db.doc(`funFacts/${req.params.id}`);
    const snap = await docRef.get();
    if (!snap.exists) return res.status(404).json({ error: 'Fun fact not found' });
 
    const fields = {};
    if (body.text !== undefined) fields.text = body.text;
    Iif (body.category !== undefined) fields.category = body.category;
    Iif (body.emoji !== undefined) fields.emoji = body.emoji;
    if (body.sourceLanguage !== undefined || body.source_language !== undefined) {
      fields.sourceLanguage = body.sourceLanguage ?? body.source_language;
    }
    if (body.isActive !== undefined || body.is_active !== undefined) {
      fields.isActive = !!(body.isActive ?? body.is_active);
    }
 
    if (Object.keys(fields).length === 0) {
      return res.status(400).json({ error: 'No fields to update' });
    }
 
    fields.updatedAt = now();
    await docRef.update(fields);
 
    return res.json({ success: true });
  } catch (err) {
    log.error('fun-facts', 'Failed to update fun fact', {
      factId: req.params.id,
      error: err.message,
    });
    return res.status(500).json({ error: 'Internal server error' });
  }
});
 
// -- Delete fact (admin) --
router.delete('/admin/fun-facts/:id', async (req, res) => {
  try {
    if (requireAdmin(req, res)) return;
 
    const docRef = db.doc(`funFacts/${req.params.id}`);
    const snap = await docRef.get();
    if (!snap.exists) return res.status(404).json({ error: 'Fun fact not found' });
 
    await docRef.delete();
 
    return res.json({ success: true });
  } catch (err) {
    log.error('fun-facts', 'Failed to delete fun fact', {
      factId: req.params.id,
      error: err.message,
    });
    return res.status(500).json({ error: 'Internal server error' });
  }
});
 
module.exports = router;