Repository navigation
Expand file tree
/
Copy pathuser.ts
More file actions
314 lines (241 loc) · 8.16 KB
/
Copy pathuser.ts
File metadata and controls
314 lines (241 loc) · 8.16 KB
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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
import axios from "axios";
import { AuthenticatedRequest } from "../middleware/auth.js";
import getBuffer from "../utils/buffer.js";
import { sql } from "../utils/db.js";
import ErrorHandler from "../utils/errorHandler.js";
import { TryCatch } from "../utils/TryCatch.js";
export const myProfile = TryCatch(async (req: AuthenticatedRequest, res, next) => {
const user = req.user;
res.json(user);
});
export const getUserProfile = TryCatch(async (req, res, next) => {
const { userId } = req.params;
const users = await sql`
SELECT
u.user_id,
u.name,
u.email,
u.phone_number,
u.role,
u.bio,
u.resume,
u.resume_public_id,
u.profile_pic,
u.profile_pic_public_id,
u.subscription,
ARRAY_AGG(s.name) FILTER (WHERE s.name IS NOT NULL) AS skills FROM users u
LEFT JOIN user_skills us ON u.user_id = us.user_id
LEFT JOIN skills s ON us.skill_id = s.skill_id
WHERE u.user_id = ${userId}
GROUP BY u.user_id;
`;
if (users.length === 0) {
throw new ErrorHandler(404, "User not found");
}
const user = users[0];
user.skills = user.skills || [];
res.json(user);
});
export const updateUserProfile = TryCatch(async (req: AuthenticatedRequest, res) => {
const user = req.user;
if (!user) {
throw new ErrorHandler(401, "Authentication required");
}
const { name, phoneNumber, bio } = req.body;
const newName = name || user.name;
const newPhoneNumber = phoneNumber || user.phone_number;
const newBio = bio || user.bio;
const [updatedUser] = await sql`
UPDATE users SET name = ${newName}, phone_number = ${newPhoneNumber}, bio = ${newBio} WHERE user_id = ${user.user_id} RETURNING user_id, name, phone_number, bio
`;
res.json({
message: "Profile updated successfully",
updatedUser,
});
});
export const updateProfilePic = TryCatch(async (req: AuthenticatedRequest, res) => {
const user = req.user;
if (!user) {
throw new ErrorHandler(401, "Authentication required");
}
const file = req.file;
if (!file) {
throw new ErrorHandler(400, "No image file uploaded");
}
const oldPublicId = user.profile_pic_public_id;
const fileBuffer = getBuffer(file);
if (!fileBuffer || !fileBuffer.content) {
throw new ErrorHandler(500, "Failed to process uploaded file");
}
const { data: uploadResult } = await axios.post<{
url: string;
public_id: string;
}>(`${process.env.UPLOAD_SERVICE}/api/utils/upload`, {
buffer: fileBuffer.content,
public_id: oldPublicId,
});
const [updatedUser] = await sql`
UPDATE users SET profile_pic = ${uploadResult.url},profile_pic_public_id = ${uploadResult.public_id} WHERE user_id = ${user.user_id} RETURNING user_id, name, profile_pic;
`;
res.json({
message: "Profile picture updated successfully",
updatedUser,
});
});
export const updateResume = TryCatch(async (req: AuthenticatedRequest, res) => {
const user = req.user;
if (!user) {
throw new ErrorHandler(401, "Authentication required");
}
const file = req.file;
if (!file) {
throw new ErrorHandler(400, "No resume file uploaded");
}
const oldPublicId = user.resume_public_id;
const fileBuffer = getBuffer(file);
if (!fileBuffer || !fileBuffer.content) {
throw new ErrorHandler(500, "Failed to process uploaded file");
}
const { data: uploadResult } = await axios.post<{
url: string;
public_id: string;
}>(`${process.env.UPLOAD_SERVICE}/api/utils/upload`, {
buffer: fileBuffer.content,
public_id: oldPublicId,
});
const [updatedUser] = await sql`
UPDATE users SET resume = ${uploadResult.url}, resume_public_id = ${uploadResult.public_id} WHERE user_id = ${user.user_id} RETURNING user_id, name, resume;
`;
res.json({
message: "Resume updated successfully",
updatedUser,
});
});
export const addSkillToUser = TryCatch(async (req: AuthenticatedRequest, res) => {
const userId = req.user?.user_id;
const { skillName } = req.body;
if (!skillName || skillName.trim() === "") {
throw new ErrorHandler(400, "Please provide a skill name");
}
let wasSkillAdded = false;
try {
await sql`BEGIN`;
const users = await sql`SELECT user_id FROM users WHERE user_id = ${userId}`;
if (users.length === 0) {
throw new ErrorHandler(404, "User not found");
}
const [skill] =
await sql`INSERT INTO skills (name) VALUES (${skillName.trim()}) ON CONFLICT (name) DO UPDATE SET name = EXCLUDED.name RETURNING skill_id;`;
const skillId = skill.skill_id;
const insertionResult = await sql`
INSERT INTO user_skills (user_id, skill_id) VALUES (${userId}, ${skillId}) ON CONFLICT (user_id, skill_id) DO NOTHING RETURNING user_id;`;
if (insertionResult.length > 0) {
wasSkillAdded = true;
}
await sql`COMMIT`;
} catch (error) {
await sql`ROLLBACK`;
throw error;
}
if (!wasSkillAdded) {
return res.status(200).json({
message: "User already has this skill",
});
}
res.json({
message: `Skill ${skillName.trim()} added to user successfully`,
});
});
export const deleteSkillFromUser = TryCatch(async (req: AuthenticatedRequest, res) => {
const user = req.user;
if (!user) {
throw new ErrorHandler(401, "Authentication required");
}
const { skillName } = req.body;
if (!skillName || skillName.trim() === "") {
throw new ErrorHandler(400, "Please provide a skill name");
}
const result = await sql`
DELETE FROM user_skills WHERE user_id = ${user.user_id} AND skill_id = (SELECT skill_id FROM skills WHERE name = ${skillName.trim()}) RETURNING user_id;
`;
if (result.length === 0) {
throw new ErrorHandler(404, ` Skill ${skillName.trim()} not found for user`);
}
res.json({
message: `Skill ${skillName.trim()} deleted successfully`,
});
});
export const applyForJob = TryCatch(async (req: AuthenticatedRequest, res) => {
const user = req.user;
if (!user) {
throw new ErrorHandler(401, "Authentication required");
}
if (user.role !== "jobseeker") {
throw new ErrorHandler(403, "Forbidden: Only jobseekers can apply for jobs");
}
const applicant_id = user.user_id;
const resume = user.resume;
if (!resume) {
throw new ErrorHandler(
400,
"Please upload your resume before applying for a job",
);
}
const { job_id } = req.body;
if (!job_id) {
throw new ErrorHandler(400, "Job ID is required to apply for a job");
}
const [job] = await sql`SELECT is_active, openings FROM jobs WHERE job_id = ${job_id}`;
if (!job) {
throw new ErrorHandler(404, "Job not found");
}
if (!job.is_active) {
throw new ErrorHandler(400, "Cannot apply for an inactive job");
}
if (job.openings <= 0) {
throw new ErrorHandler(400, "No openings available for this job");
}
const now = Date.now();
const subTime = req.user?.subscription
? new Date(req.user.subscription).getTime()
: 0;
const isSubscribed = subTime > now;
let newApplication;
try {
[newApplication] = await sql`
INSERT INTO applications (job_id, applicant_id, applicant_email, resume, subscribed) VALUES (${job_id}, ${applicant_id}, ${user?.email}, ${resume}, ${isSubscribed})`;
// Decrement openings count after successful application
await sql`UPDATE jobs SET openings = openings - 1 WHERE job_id = ${job_id} AND openings > 0`;
} catch (error: any) {
if (error.code === "23505") {
throw new ErrorHandler(409, "You have already applied for this job");
}
throw error;
}
res.json({
message: "Application submitted successfully",
application: newApplication,
});
});
export const getAllaplications = TryCatch(async (req: AuthenticatedRequest, res) => {
const skip = parseInt(req.query.skip as string) || 0;
const limit = parseInt(req.query.limit as string) || 10;
// Get total count
const countResult = await sql`
SELECT COUNT(*) as total FROM applications WHERE applicant_id = ${req.user?.user_id}`;
const total = countResult[0]?.total || 0;
// Get paginated applications
const applications = await sql`
SELECT a.*, j.title AS job_title, j.salary AS job_salary, j.location AS job_location
FROM applications a
JOIN jobs j ON a.job_id = j.job_id
WHERE a.applicant_id = ${req.user?.user_id}
ORDER BY a.applied_at DESC
LIMIT ${limit} OFFSET ${skip}`;
res.json({
applications,
total,
skip,
limit,
});
});