Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: implement update user route. #443

Open
wants to merge 3 commits into
base: clean
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,4 @@ GOOGLE_AUTH_CALLBACK_URL=
FLW_PUBLIC_KEY=
FLW_SECRET_KEY=
FLW_ENCRYPTION_KEY=
BASE_URL=
BASE_URL=
150 changes: 149 additions & 1 deletion src/controllers/UserController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ class UserController {
profile_id: user?.profile?.id,
username: user?.profile?.username,
bio: user?.profile?.bio,
job_title: user?.profile?.jobTitle,
jobTitle: user?.profile?.jobTitle,
language: user?.profile?.language,
pronouns: user?.profile?.pronouns,
department: user?.profile?.department,
Expand All @@ -99,6 +99,154 @@ class UserController {
);
},
);

/**
* @swagger
* /api/v1/users/me:
* put:
* tags:
* - User
* summary: Update User profile
* security:
* - bearerAuth: []
* description: Api endpoint to update the profile data of the currently authenticated user.
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* first_name:
* type: string
* example: Yasuke
* last_name:
* type: string
* example: Shimazu
* username:
* type: string
* example: yasuke
* bio:
* type: string
* example: Samurai from Africa
* jobTitle:
* type: string
* example: Warrior
* language:
* type: string
* example: Japanese
* pronouns:
* type: string
* example: he/him
* department:
* type: string
* example: Military
* social_links:
* type: array
* items:
* type: string
* example: ["https://twitter.com/yasuke"]
* timezones:
* type: string
* example: "Asia/Tokyo"
* responses:
* 200:
* description: Updated User profile Successfully
* content:
* application/json:
* schema:
* type: object
* properties:
* status_code:
* type: integer
* example: 200
* data:
* type: object
* properties:
* id:
* type: string
* example: 58b6
* user_name:
* type: string
* example: yasuke
* email:
* type: string
* example: [email protected]
* profile_picture:
* type: string
* example: https://avatar.com
* 400:
* description: Bad request
* 401:
* description: Unauthorized access
* 404:
* description: Not found
* 500:
* description: Internal Server Error
*
*/
static updateProfile = asyncHandler(
async (req: Request, res: Response, next: NextFunction) => {
const { id } = req.user;
const {
first_name,
last_name,
username,
bio,
jobTitle,
language,
pronouns,
department,
social_links,
timezones,
} = req.body;

if (!id) {
throw new BadRequest("Unauthorized! No ID provided");
}

if (!validate(id)) {
throw new BadRequest("Unauthorized! Invalid User Id Format");
}

const user = await UserService.getUserById(id);
if (!user) {
throw new ResourceNotFound("User Not Found!");
}

if (user?.deletedAt || user?.is_deleted) {
throw new ResourceNotFound("User not found!");
}

const updatedUser = await UserService.updateUserById(id, {
first_name,
last_name,
username,
bio,
jobTitle,
language,
pronouns,
department,
social_links,
timezones,
});

sendJsonResponse(res, 200, "User profile updated successfully", {
id: updatedUser.id,
first_name: updatedUser?.first_name,
last_name: updatedUser?.last_name,
profile_id: updatedUser?.profile?.id,
username: updatedUser?.profile?.username,
bio: updatedUser?.profile?.bio,
jobTitle: updatedUser?.profile?.jobTitle,
language: updatedUser?.profile?.language,
pronouns: updatedUser?.profile?.pronouns,
department: updatedUser?.profile?.department,
social_links: updatedUser?.profile?.social_links,
timezones: updatedUser?.profile?.timezones,
});
},
);
}

export { UserController };
1 change: 1 addition & 0 deletions src/routes/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ import { authMiddleware } from "../middleware";
const userRoute = Router();

userRoute.get("/users/me", authMiddleware, UserController.getProfile);
userRoute.put("/users/me", authMiddleware, UserController.updateProfile);

export { userRoute };
28 changes: 28 additions & 0 deletions src/services/userservice.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { User } from "../models/user";
import { Profile } from "../models/profile";
import AppDataSource from "../data-source";
import { ResourceNotFound } from "../middleware";

Expand All @@ -17,4 +18,31 @@ export class UserService {

return user;
}

static async updateUserById(
id: string,
updateData: Partial<User & Profile>,
): Promise<User> {
const userRepository = AppDataSource.getRepository(User);
let user = await userRepository.findOne({
where: { id },
relations: ["profile"],
});

if (!user) {
throw new ResourceNotFound("User Not Found!");
}

// Update user fields with provided data
if (user.profile) {
Object.assign(user.profile, updateData.profile);
await userRepository.save(user.profile);
}
Object.assign(user, updateData);

// Save updated user to the database
await userRepository.save(user);

return user;
}
}
114 changes: 114 additions & 0 deletions src/test/UserController.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import request from "supertest";
import app from "../app";
import { UserService } from "../services";
import { User } from "../models";
import AppDataSource from "../data-source";
import { BadRequest } from "../middleware";

jest.mock("../services/UserService");

describe("UserController.updateProfile", () => {
let userId: string;
let mockUser: User;

beforeAll(async () => {
if (!AppDataSource.isInitialized) {
await AppDataSource.initialize();
}

userId = "valid-user-id";
mockUser = {
id: userId,
first_name: "John",
last_name: "Doe",
profile: {
id: "profile-id",
username: "johndoe",
bio: "A short bio",
jobTitle: "Developer",
language: "English",
pronouns: "he/him",
department: "Engineering",
social_links: ["https://twitter.com/johndoe"],
timezones: "UTC",
},
} as User;
});

afterAll(async () => {
if (AppDataSource.isInitialized) {
await AppDataSource.destroy();
}
});

it("should update the user profile successfully", async () => {
(UserService.getUserById as jest.Mock).mockResolvedValue(mockUser);
(UserService.updateUserById as jest.Mock).mockResolvedValue({
...mockUser,
first_name: "Jane",
});

const response = await request(app)
.put("/api/v1/users/me")
.set("Authorization", `Bearer valid-token`)
.send({
first_name: "Jane",
last_name: "Doe",
username: "janedoe",
bio: "A new bio",
jobTitle: "Senior Developer",
language: "French",
pronouns: "she/her",
department: "Engineering",
social_links: ["https://twitter.com/janedoe"],
timezones: "CET",
});

expect(response.status).toBe(200);
expect(response.body.data).toMatchObject({
id: userId,
first_name: "Jane",
last_name: "Doe",
profile_id: "profile-id",
username: "janedoe",
bio: "A new bio",
jobTitle: "Senior Developer",
language: "French",
pronouns: "she/her",
department: "Engineering",
social_links: ["https://twitter.com/janedoe"],
timezones: "CET",
});
});

it("should return 404 if user is not found", async () => {
(UserService.getUserById as jest.Mock).mockResolvedValue(null);

const response = await request(app)
.put("/api/v1/users/me")
.set("Authorization", `Bearer valid-token`)
.send({
first_name: "Jane",
last_name: "Doe",
});

expect(response.status).toBe(404);
expect(response.body.message).toBe("User Not Found!");
});

it("should return 400 for invalid user ID format", async () => {
(UserService.getUserById as jest.Mock).mockImplementation(() => {
throw new BadRequest("Unauthorized! Invalid User Id Format");
});

const response = await request(app)
.put("/api/v1/users/me")
.set("Authorization", `Bearer valid-token`)
.send({
first_name: "Jane",
});

expect(response.status).toBe(400);
expect(response.body.message).toBe("Unauthorized! Invalid User Id Format");
});
});
Loading