File size: 3,355 Bytes
7e49c4d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import { z } from 'zod';
import db from '../utils/firebaseClient.js';
import { AppError } from '../middleware/errorHandler.js';
import { sanitizeText } from '../utils/sanitize.js';
import crypto from 'crypto';

const createUserSchema = z.object({
  name: z.string().min(2, 'Name must be at least 2 characters').max(100),
  email: z.string().email('Invalid email address'),
});

export const createUser = async (req, res, next) => {
  try {
    const { name, email } = createUserSchema.parse(req.body);
    const sanitizedName = sanitizeText(name);
    const sanitizedEmail = sanitizeText(email).toLowerCase();

    const usersRef = db.collection('users');
    const qSnapshot = await usersRef.where('email', '==', sanitizedEmail).get();

    let userId;
    let userData;

    if (!qSnapshot.empty) {
      const userDoc = qSnapshot.docs[0];
      userId = userDoc.id;
      userData = {
        ...userDoc.data(),
        name: sanitizedName,
        updatedAt: new Date().toISOString(),
      };
      await usersRef.doc(userId).set(userData);
    } else {
      userId = crypto.randomUUID();
      userData = {
        id: userId,
        name: sanitizedName,
        email: sanitizedEmail,
        points: 0,
        badges: [],
        title: 'Eco Novice',
        streak: { current: 0, longest: 0, lastSaved: null },
        createdAt: new Date().toISOString(),
        updatedAt: new Date().toISOString(),
      };
      await usersRef.doc(userId).set(userData);
    }

    const assessmentsSnapshot = await db
      .collection('assessments')
      .where('userId', '==', userId)
      .get();
    const assessmentsCount = assessmentsSnapshot.docs.length;

    res.status(201).json({
      success: true,
      data: {
        ...userData,
        id: userId,
        _count: { assessments: assessmentsCount },
      },
    });
  } catch (err) {
    next(err);
  }
};

export const getUserById = async (req, res, next) => {
  try {
    const { id } = req.params;
    const sanitizedId = sanitizeText(id);

    const userDoc = await db.collection('users').doc(sanitizedId).get();
    if (!userDoc.exists) {
      throw new AppError('User not found', 404);
    }

    const userData = userDoc.data();
    const assessmentsSnapshot = await db
      .collection('assessments')
      .where('userId', '==', sanitizedId)
      .get();
    const assessmentsCount = assessmentsSnapshot.docs.length;

    res.json({
      success: true,
      data: {
        ...userData,
        id: sanitizedId,
        _count: { assessments: assessmentsCount },
      },
    });
  } catch (err) {
    next(err);
  }
};

export const getLeaderboard = async (req, res, next) => {
  try {
    const page = parseInt(req.query.page) || 1;
    const limit = parseInt(req.query.limit) || 10;
    const offset = (page - 1) * limit;

    const usersSnap = await db.collection('users').get();
    const usersList = usersSnap.docs.map((doc) => ({ id: doc.id, ...doc.data() }));

    usersList.sort((a, b) => (b.points || 0) - (a.points || 0));

    const total = usersList.length;
    const paginatedUsers = usersList.slice(offset, offset + limit);

    res.json({
      success: true,
      data: paginatedUsers,
      pagination: {
        page,
        limit,
        total,
        totalPages: Math.ceil(total / limit),
      },
    });
  } catch (err) {
    next(err);
  }
};