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: i18n support for backend #1647

Open
wants to merge 13 commits into
base: develop
Choose a base branch
from
16 changes: 12 additions & 4 deletions Docker/dist/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
services:
client:
image: bluewaveuptime/uptime_client:latest
build:
context: ../../
dockerfile: Docker/dist/client.Dockerfile
restart: always
environment:
UPTIME_APP_API_BASE_URL: "http://localhost:5000/api/v1"
Expand All @@ -10,7 +12,9 @@ services:
depends_on:
- server
server:
image: bluewaveuptime/uptime_server:latest
build:
context: ../../
dockerfile: Docker/dist/server.Dockerfile
restart: always
ports:
- "5000:5000"
Expand All @@ -23,7 +27,9 @@ services:
# volumes:
# - /var/run/docker.sock:/var/run/docker.sock:ro
redis:
image: bluewaveuptime/uptime_redis:latest
build:
context: ../../
dockerfile: Docker/dist/redis.Dockerfile
restart: always
ports:
- "6379:6379"
Expand All @@ -36,7 +42,9 @@ services:
retries: 5
start_period: 5s
mongodb:
image: bluewaveuptime/uptime_database_mongo:latest
build:
context: ../../
dockerfile: Docker/dist/mongoDB.Dockerfile
restart: always
volumes:
- ./mongo/data:/data/db
Expand Down
49 changes: 25 additions & 24 deletions Server/controllers/authController.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@ import { handleValidationError, handleError } from "./controllerUtils.js";
const SERVICE_NAME = "authController";

class AuthController {
constructor(db, settingsService, emailService, jobQueue) {
constructor(db, settingsService, emailService, jobQueue, stringService) {
this.db = db;
this.settingsService = settingsService;
this.emailService = emailService;
this.jobQueue = jobQueue;
this.stringService = stringService;
}

/**
Expand Down Expand Up @@ -76,7 +77,7 @@ class AuthController {
// If superAdmin exists, a token should be attached to all further register requests
const superAdminExists = await this.db.checkSuperadmin(req, res);
if (superAdminExists) {
await this.db.getInviteTokenAndDelete(inviteToken);
await this.db.getInviteTokenAndDelete(inviteToken, req.language);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't need req.language anymore, you can pass a reference to StringService to MongoDB when it is instantiated

} else {
// This is the first account, create JWT secret to use if one is not supplied by env
const jwtSecret = crypto.randomBytes(64).toString("hex");
Expand All @@ -85,7 +86,7 @@ class AuthController {

const newUser = await this.db.insertUser({ ...req.body }, req.file);
logger.info({
message: successMessages.AUTH_CREATE_USER,
message: successMessages.AUTH_CREATE_USER(req.language),
service: SERVICE_NAME,
details: newUser._id,
});
Expand Down Expand Up @@ -116,7 +117,7 @@ class AuthController {
});

res.success({
msg: successMessages.AUTH_CREATE_USER,
msg: successMessages.AUTH_CREATE_USER(req.language),
data: { user: newUser, token: token, refreshToken: refreshToken },
});
} catch (error) {
Expand Down Expand Up @@ -148,12 +149,12 @@ class AuthController {
const { email, password } = req.body;

// Check if user exists
const user = await this.db.getUserByEmail(email);
const user = await this.db.getUserByEmail(email, req.language);

// Compare password
const match = await user.comparePassword(password);
if (match !== true) {
const error = new Error(errorMessages.AUTH_INCORRECT_PASSWORD);
const error = new Error(this.stringService.authIncorrectPassword);
error.status = 401;
next(error);
return;
Comment on lines +156 to 159
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Standardize error handling patterns across the controller.

Error creation is scattered throughout the code with inconsistent patterns. Some errors include service and method information, while others don't.

Consider creating a helper method in controllerUtils.js:

+ /**
+  * Creates a standardized error with i18n support
+  * @param {string} message - The i18n message key
+  * @param {number} status - HTTP status code
+  * @param {string} service - Service name
+  * @param {string} method - Method name
+  * @returns {Error}
+  */
+ export const createError = (message, status, service, method) => {
+   const error = new Error(message);
+   error.status = status;
+   error.service = service;
+   error.method = method;
+   return error;
+ };

Then use it consistently:

- const error = new Error(this.stringService.authIncorrectPassword);
- error.status = 401;
- next(error);
+ next(createError(
+   this.stringService.authIncorrectPassword,
+   401,
+   SERVICE_NAME,
+   'loginUser'
+ ));

Also applies to: 210-215, 225-231, 281-286, 305-309

Expand All @@ -176,7 +177,7 @@ class AuthController {
userWithoutPassword.avatarImage = user.avatarImage;

return res.success({
msg: successMessages.AUTH_LOGIN_USER,
msg: successMessages.AUTH_LOGIN_USER(req.language),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same for these, these strings should now all be provided by the StringService

data: {
user: userWithoutPassword,
token: token,
Expand Down Expand Up @@ -206,7 +207,7 @@ class AuthController {

if (!refreshToken) {
// No refresh token provided
const error = new Error(errorMessages.NO_REFRESH_TOKEN);
const error = new Error(this.stringService.noRefreshToken);
error.status = 401;
error.service = SERVICE_NAME;
error.method = "refreshAuthToken";
Expand All @@ -221,8 +222,8 @@ class AuthController {
// Invalid or expired refresh token, trigger logout
const errorMessage =
refreshErr.name === "TokenExpiredError"
? errorMessages.EXPIRED_REFRESH_TOKEN
: errorMessages.INVALID_REFRESH_TOKEN;
? this.stringService.expiredAuthToken
: this.stringService.invalidAuthToken;
const error = new Error(errorMessage);
error.status = 401;
error.service = SERVICE_NAME;
Expand All @@ -243,7 +244,7 @@ class AuthController {
);

return res.success({
msg: successMessages.AUTH_TOKEN_REFRESHED,
msg: successMessages.AUTH_TOKEN_REFRESHED(req.language),
data: { user: payloadData, token: newAuthToken, refreshToken: refreshToken },
});
} catch (error) {
Expand Down Expand Up @@ -276,7 +277,7 @@ class AuthController {

// TODO is this neccessary any longer? Verify ownership middleware should handle this
if (req.params.userId !== req.user._id.toString()) {
const error = new Error(errorMessages.AUTH_UNAUTHORIZED);
const error = new Error(this.stringService.unauthorized);
error.status = 401;
error.service = SERVICE_NAME;
next(error);
Expand All @@ -294,13 +295,13 @@ class AuthController {
// Add user email to body for DB operation
req.body.email = email;
// Get user
const user = await this.db.getUserByEmail(email);
const user = await this.db.getUserByEmail(email, req.language);
// Compare passwords
const match = await user.comparePassword(req.body.password);
// If not a match, throw a 403
// 403 instead of 401 to avoid triggering axios interceptor
if (!match) {
const error = new Error(errorMessages.AUTH_INCORRECT_PASSWORD);
const error = new Error(this.stringService.authIncorrectPassword);
error.status = 403;
next(error);
return;
Expand All @@ -311,7 +312,7 @@ class AuthController {

const updatedUser = await this.db.updateUser(req, res);
res.success({
msg: successMessages.AUTH_UPDATE_USER,
msg: successMessages.AUTH_UPDATE_USER(req.language),
data: updatedUser,
});
} catch (error) {
Expand All @@ -333,7 +334,7 @@ class AuthController {
const superAdminExists = await this.db.checkSuperadmin(req, res);

return res.success({
msg: successMessages.AUTH_ADMIN_EXISTS,
msg: successMessages.AUTH_ADMIN_EXISTS(req.language),
data: superAdminExists,
});
} catch (error) {
Expand Down Expand Up @@ -362,7 +363,7 @@ class AuthController {

try {
const { email } = req.body;
const user = await this.db.getUserByEmail(email);
const user = await this.db.getUserByEmail(email, req.language);
const recoveryToken = await this.db.requestRecoveryToken(req, res);
const name = user.firstName;
const { clientHost } = this.settingsService.getSettings();
Expand All @@ -379,7 +380,7 @@ class AuthController {
);

return res.success({
msg: successMessages.AUTH_CREATE_RECOVERY_TOKEN,
msg: successMessages.AUTH_CREATE_RECOVERY_TOKEN(req.language),
data: msgId,
});
} catch (error) {
Expand Down Expand Up @@ -410,7 +411,7 @@ class AuthController {
await this.db.validateRecoveryToken(req, res);

return res.success({
msg: successMessages.AUTH_VERIFY_RECOVERY_TOKEN,
msg: successMessages.AUTH_VERIFY_RECOVERY_TOKEN(req.language),
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "validateRecoveryTokenController"));
Expand Down Expand Up @@ -443,7 +444,7 @@ class AuthController {
const token = this.issueToken(user._doc, tokenType.ACCESS_TOKEN, appSettings);

return res.success({
msg: successMessages.AUTH_RESET_PASSWORD,
msg: successMessages.AUTH_RESET_PASSWORD(req.language),
data: { user, token },
});
} catch (error) {
Expand All @@ -467,7 +468,7 @@ class AuthController {
const { email } = decodedToken;

// Check if the user exists
const user = await this.db.getUserByEmail(email);
const user = await this.db.getUserByEmail(email, req.language);
// 1. Find all the monitors associated with the team ID if superadmin

const result = await this.db.getMonitorsByTeamId({
Expand All @@ -494,10 +495,10 @@ class AuthController {
await this.db.deleteMonitorsByUserId(user._id);
}
// 6. Delete the user by id
await this.db.deleteUser(user._id);
await this.db.deleteUser(user._id, req.language);

return res.success({
msg: successMessages.AUTH_DELETE_USER,
msg: successMessages.AUTH_DELETE_USER(req.language),
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "deleteUserController"));
Expand All @@ -509,7 +510,7 @@ class AuthController {
const allUsers = await this.db.getAllUsers(req, res);

return res.success({
msg: successMessages.AUTH_GET_ALL_USERS,
msg: successMessages.AUTH_GET_ALL_USERS(req.language),
data: allUsers,
});
} catch (error) {
Expand Down
12 changes: 6 additions & 6 deletions Server/controllers/checkController.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class CheckController {
const check = await this.db.createCheck(checkData);

return res.success({
msg: successMessages.CHECK_CREATE,
msg: successMessages.CHECK_CREATE(req.language),
data: check,
});
} catch (error) {
Expand All @@ -57,7 +57,7 @@ class CheckController {
const result = await this.db.getChecksByMonitor(req);

return res.success({
msg: successMessages.CHECK_GET,
msg: successMessages.CHECK_GET(req.language),
data: result,
});
} catch (error) {
Expand All @@ -77,7 +77,7 @@ class CheckController {
const checkData = await this.db.getChecksByTeam(req);

return res.success({
msg: successMessages.CHECK_GET,
msg: successMessages.CHECK_GET(req.language),
data: checkData,
});
} catch (error) {
Expand All @@ -97,7 +97,7 @@ class CheckController {
const deletedCount = await this.db.deleteChecks(req.params.monitorId);

return res.success({
msg: successMessages.CHECK_DELETE,
msg: successMessages.CHECK_DELETE(req.language),
data: { deletedCount },
});
} catch (error) {
Expand All @@ -117,7 +117,7 @@ class CheckController {
const deletedCount = await this.db.deleteChecksByTeamId(req.params.teamId);

return res.success({
msg: successMessages.CHECK_DELETE,
msg: successMessages.CHECK_DELETE(req.language),
data: { deletedCount },
});
} catch (error) {
Expand All @@ -144,7 +144,7 @@ class CheckController {
await this.db.updateChecksTTL(teamId, ttl);

return res.success({
msg: successMessages.CHECK_UPDATE_TTL,
msg: successMessages.CHECK_UPDATE_TTL(req.language),
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "updateTTL"));
Expand Down
6 changes: 3 additions & 3 deletions Server/controllers/inviteController.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ class InviteController {
});

return res.success({
msg: successMessages.INVITE_ISSUED,
msg: successMessages.INVITE_ISSUED(req.language),
data: inviteToken,
});
} catch (error) {
Expand All @@ -83,10 +83,10 @@ class InviteController {
}

try {
const invite = await this.db.getInviteToken(req.body.token);
const invite = await this.db.getInviteToken(req.body.token, req.language);

return res.success({
msg: successMessages.INVITE_VERIFIED,
msg: successMessages.INVITE_VERIFIED(req.language),
data: invite,
});
} catch (error) {
Expand Down
12 changes: 6 additions & 6 deletions Server/controllers/maintenanceWindowController.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class MaintenanceWindowController {
await Promise.all(dbTransactions);

return res.success({
msg: successMessages.MAINTENANCE_WINDOW_CREATE,
msg: successMessages.MAINTENANCE_WINDOW_CREATE(req.language),
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "createMaintenanceWindow"));
Expand All @@ -63,7 +63,7 @@ class MaintenanceWindowController {
const maintenanceWindow = await this.db.getMaintenanceWindowById(req.params.id);

return res.success({
msg: successMessages.MAINTENANCE_WINDOW_GET_BY_ID,
msg: successMessages.MAINTENANCE_WINDOW_GET_BY_ID(req.language),
data: maintenanceWindow,
});
} catch (error) {
Expand All @@ -89,7 +89,7 @@ class MaintenanceWindowController {
);

return res.success({
msg: successMessages.MAINTENANCE_WINDOW_GET_BY_TEAM,
msg: successMessages.MAINTENANCE_WINDOW_GET_BY_TEAM(req.language),
data: maintenanceWindows,
});
} catch (error) {
Expand All @@ -111,7 +111,7 @@ class MaintenanceWindowController {
);

return res.success({
msg: successMessages.MAINTENANCE_WINDOW_GET_BY_USER,
msg: successMessages.MAINTENANCE_WINDOW_GET_BY_USER(req.language),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Fix inconsistent message key.

The message key MAINTENANCE_WINDOW_GET_BY_USER doesn't match the method name getMaintenanceWindowsByMonitorId. This could cause confusion.

-				msg: successMessages.MAINTENANCE_WINDOW_GET_BY_USER(req.language),
+				msg: successMessages.MAINTENANCE_WINDOW_GET_BY_MONITOR(req.language),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
msg: successMessages.MAINTENANCE_WINDOW_GET_BY_USER(req.language),
msg: successMessages.MAINTENANCE_WINDOW_GET_BY_MONITOR(req.language),

data: maintenanceWindows,
});
} catch (error) {
Expand All @@ -129,7 +129,7 @@ class MaintenanceWindowController {
try {
await this.db.deleteMaintenanceWindowById(req.params.id);
return res.success({
msg: successMessages.MAINTENANCE_WINDOW_DELETE,
msg: successMessages.MAINTENANCE_WINDOW_DELETE(req.language),
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "deleteMaintenanceWindow"));
Expand All @@ -150,7 +150,7 @@ class MaintenanceWindowController {
req.body
);
return res.success({
msg: successMessages.MAINTENANCE_WINDOW_EDIT,
msg: successMessages.MAINTENANCE_WINDOW_EDIT(req.language),
data: editedMaintenanceWindow,
});
} catch (error) {
Expand Down
Loading