eletrotupi / tcc/ api/src/controllers/users.ts4afa6690e7fdf56346236182ffc75bb984eb9441
5.2 KB | 212 lines Raw
1
import { Request, Response, NextFunction } from 'express';
2
import {
3
  generateToken
4
} from '@app/services/auth.service';
5

6
import { getQueue, MailJobName } from '@app/lib/queue';
7

8
import {
9
  CreateUserSchema,
10
  UpdateUserSchema,
11
  UpdateUserPreferencesSchema,
12
} from '@app/schemas';
13
import { formatValidationError } from '@app/lib/errors/validationError';
14
import { syncDailyReminderJob } from '@app/services/dailyReminder.sync';
15

16
import {
17
  createUser,
18
  findUserById,
19
  findUserByEmail,
20
  updateUser
21
} from '@app/services/user.service';
22

23
import {
24
  storeActivationCode
25
} from '@app/services/auth.service';
26

27
import {
28
  AuthenticatedRequest
29
} from '@app/middleware/auth';
30

31
import { prisma } from '@app/lib/prisma';
32
import s3 from '@app/lib/s3';
33
import { DeleteObjectCommand } from '@aws-sdk/client-s3';
34
import crypto from 'crypto';
35
import { addMinutes } from 'date-fns';
36

37
// XXX: I've regreted already
38
interface SingleFileRequest extends AuthenticatedRequest {
39
  file?: any;
40
}
41

42
export const UsersController = {
43
  create: async (req: Request, res: Response, next: NextFunction) => {
44
    try {
45
      const parsed = CreateUserSchema.safeParse(req.body.user);
46

47
      if (!parsed.success) {
48
        return res.status(400).json(formatValidationError(parsed.error!));
49
      }
50

51
      const user = await createUser(parsed.data);
52
      const jwtToken = generateToken(user.id, user.email);
53

54
      const code = [...Array(6)].map(() => crypto.randomInt(9))
55
        .join("");
56

57
      const expiresAt = addMinutes(new Date(), 5);
58
      await storeActivationCode(user.id, code, expiresAt);
59

60
      const mailQueue = getQueue('mail');
61
      const job = await mailQueue.add(MailJobName.WelcomeEmail, {
62
        userId: user.id,
63
        code: code
64
      });
65

66
      res.status(201).json({
67
        token: jwtToken
68
      });
69
    } catch (err) {
70
      next(err);
71
    }
72
  },
73

74
  update: async (req: Request, res: Response, next: NextFunction) => {
75
    try {
76
      const { id } = req.params;
77

78
      const parsed = UpdateUserSchema.safeParse(req.body.user);
79
      if (!parsed.success) {
80
        return res.status(400).json(formatValidationError(parsed.error!));
81
      }
82

83
      const user = await findUserById(Number(id));
84

85
      if (!user) {
86
        return res.status(404).json({
87
          error: "Usuário não encontrado"
88
        });
89
      }
90

91
      const updated = await updateUser(parsed.data);
92

93
      // TODO: Drop encrypted password & token here
94
      return res.status(200).json({
95
        user: updated
96
      });
97
    } catch (err) {
98
      next(err);
99
    }
100
  },
101

102
  updateAvatar: async (req: SingleFileRequest, res: Response, next: NextFunction) => {
103
    try {
104
      if (!req.file) {
105
        return res.status(400).json({ error: 'No file uploaded' });
106
      }
107

108
      const currentUser = await findUserById(Number(req.userId));
109
      if (currentUser?.avatarKey) {
110
        await s3.send(
111
          new DeleteObjectCommand({
112
            Bucket: process.env.S3_BUCKET!, Key: currentUser?.avatarKey!
113
          })
114
        );
115
      }
116

117
      const user = await prisma.user.update({
118
        where: {
119
          id: currentUser?.id
120
        }, data: {
121
          avatarURL: req.file.location,
122
          avatarKey: req.file.key
123
        }
124
      });
125

126
      res.json({
127
        message: 'File uploaded successfully',
128
        fileLocation: req.file.location,
129
        key: req.file.key,
130
        filename: req.file.originalname,
131
        size: req.file.size,
132
        user: user
133
      });
134
    } catch (err) {
135
      next(err);
136
    }
137
  },
138

139
  updatePreferences: async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
140
    try {
141
      const parsed = UpdateUserPreferencesSchema.safeParse(req.body.user);
142
      if (!parsed.success) {
143
        return res.status(400).json(formatValidationError(parsed.error!));
144
      }
145

146
      const user = await prisma.user.update({
147
        where: { id: req.userId },
148
        data: parsed.data,
149
      });
150

151
      await syncDailyReminderJob(user);
152

153
      return res.status(200).json({ user });
154
    } catch (err) {
155
      next(err);
156
    }
157
  },
158

159
  me: async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
160
    try {
161
      const user = await prisma.user.findUnique({
162
        where: { id: req.userId! },
163
        select: {
164
          id: true,
165
          email: true,
166
          firstName: true,
167
          lastName: true,
168
          updatedAt: true,
169
          avatarKey: true,
170
          avatarURL: true,
171
          active: true,
172
          pushToken: true,
173
          notificationsEnabled: true,
174
          dailyReminderTime: true,
175
        },
176
      });
177

178
      if (!user) return res.status(404).json({ error: "User not found" });
179

180
      return res.json({ user });
181
    } catch (err) {
182
      next(err);
183
    }
184
  },
185

186
  removeAvatar: async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
187
    const currentUser = await findUserById(Number(req.userId));
188

189
    if (currentUser && currentUser?.avatarKey == null) {
190
      return res.json({ user: currentUser })
191
    }
192

193
    await s3.send(
194
      new DeleteObjectCommand({
195
        Bucket: process.env.S3_BUCKET!, Key: currentUser?.avatarKey!
196
      })
197
    );
198

199
    const user = await prisma.user.update({
200
      where: {
201
        id: req.userId
202
      }, data: {
203
        avatarURL: null,
204
        avatarKey: null
205
      }
206
    });
207

208
    return res.json({
209
      user
210
    })
211
  }
212
}