Skip to content

Commit

Permalink
Merge pull request #3913 from airqo-platform/staging
Browse files Browse the repository at this point in the history
move to production
  • Loading branch information
Baalmart authored Nov 25, 2024
2 parents eccfdc5 + 4d10a7f commit ffa7c76
Show file tree
Hide file tree
Showing 9 changed files with 143 additions and 74 deletions.
2 changes: 1 addition & 1 deletion k8s/analytics/values-prod.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ images:
celeryWorker: eu.gcr.io/airqo-250220/airqo-analytics-celery-worker
reportJob: eu.gcr.io/airqo-250220/airqo-analytics-report-job
devicesSummaryJob: eu.gcr.io/airqo-250220/airqo-analytics-devices-summary-job
tag: prod-6fe30608-1732546291
tag: prod-eccfdc59-1732550643
api:
name: airqo-analytics-api
label: analytics-api
Expand Down
2 changes: 1 addition & 1 deletion k8s/auth-service/values-prod.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ app:
replicaCount: 3
image:
repository: eu.gcr.io/airqo-250220/airqo-auth-api
tag: prod-6fe30608-1732546291
tag: prod-eccfdc59-1732550643
nameOverride: ''
fullnameOverride: ''
podAnnotations: {}
Expand Down
2 changes: 1 addition & 1 deletion k8s/auth-service/values-stage.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ app:
replicaCount: 2
image:
repository: eu.gcr.io/airqo-250220/airqo-stage-auth-api
tag: stage-e0815cc8-1731824148
tag: stage-bd23841e-1732556820
nameOverride: ''
fullnameOverride: ''
podAnnotations: {}
Expand Down
2 changes: 1 addition & 1 deletion k8s/device-registry/values-prod.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ app:
replicaCount: 3
image:
repository: eu.gcr.io/airqo-250220/airqo-device-registry-api
tag: prod-6fe30608-1732546291
tag: prod-eccfdc59-1732550643
nameOverride: ''
fullnameOverride: ''
podAnnotations: {}
Expand Down
2 changes: 1 addition & 1 deletion k8s/exceedance/values-prod-airqo.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ app:
configmap: env-exceedance-production
image:
repository: eu.gcr.io/airqo-250220/airqo-exceedance-job
tag: prod-6fe30608-1732546291
tag: prod-eccfdc59-1732550643
nameOverride: ''
fullnameOverride: ''
2 changes: 1 addition & 1 deletion k8s/exceedance/values-prod-kcca.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ app:
configmap: env-exceedance-production
image:
repository: eu.gcr.io/airqo-250220/kcca-exceedance-job
tag: prod-6fe30608-1732546291
tag: prod-eccfdc59-1732550643
nameOverride: ''
fullnameOverride: ''
2 changes: 1 addition & 1 deletion k8s/predict/values-prod.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ images:
predictJob: eu.gcr.io/airqo-250220/airqo-predict-job
trainJob: eu.gcr.io/airqo-250220/airqo-train-job
predictPlaces: eu.gcr.io/airqo-250220/airqo-predict-places-air-quality
tag: prod-6fe30608-1732546291
tag: prod-eccfdc59-1732550643
api:
name: airqo-prediction-api
label: prediction-api
Expand Down
2 changes: 1 addition & 1 deletion k8s/website/values-stage.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ app:
replicaCount: 2
image:
repository: eu.gcr.io/airqo-250220/airqo-stage-website-api
tag: stage-db11491e-1732546250
tag: stage-d205505d-1732550603
nameOverride: ''
fullnameOverride: ''
podAnnotations: {}
Expand Down
201 changes: 135 additions & 66 deletions src/auth-service/models/User.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const logger = require("log4js").getLogger(
const validUserTypes = ["user", "guest"];
const { HttpError } = require("@utils/errors");
const mailer = require("@utils/mailer");
const ORGANISATIONS_LIMIT = 6;

function oneMonthFromNow() {
var d = new Date();
Expand Down Expand Up @@ -247,14 +248,27 @@ UserSchema.path("group_roles.userType").validate(function (value) {
return validUserTypes.includes(value);
}, "Invalid userType value");

UserSchema.pre("save", function (next) {
UserSchema.pre("save", async function (next) {
// Password hashing
if (this.isModified("password")) {
this.password = bcrypt.hashSync(this.password, saltRounds);
}

// Validate contact information
if (!this.email && !this.phoneNumber) {
return next(new Error("Phone number or email is required!"));
}

// Profile picture validation
if (this.profilePicture && !validateProfilePicture(this.profilePicture)) {
return next(
new HttpError("Bad Request Error", httpStatus.BAD_REQUEST, {
message: "Invalid profile picture URL",
})
);
}

// Network roles handling
if (!this.network_roles || this.network_roles.length === 0) {
if (
!constants ||
Expand All @@ -281,6 +295,7 @@ UserSchema.pre("save", function (next) {
];
}

// Group roles handling
if (!this.group_roles || this.group_roles.length === 0) {
if (
!constants ||
Expand All @@ -292,7 +307,7 @@ UserSchema.pre("save", function (next) {
httpStatus.INTERNAL_SERVER_ERROR,
{
message:
"Contact [email protected] -- unable to retrieve the default Group or Role to which the User will belong",
"Contact [email protected] -- unable to retrieve the default Group or Role",
}
);
}
Expand All @@ -307,23 +322,119 @@ UserSchema.pre("save", function (next) {
];
}

if (!this.verified) {
this.verified = false;
// Permissions handling
if (this.permissions && this.permissions.length > 0) {
// Additional permissions validation can be added here if needed
this.permissions = [...new Set(this.permissions)]; // Ensure unique permissions
}

if (!this.analyticsVersion) {
this.analyticsVersion = 2;
}
// Ensure default values
this.verified = this.verified ?? false;
this.analyticsVersion = this.analyticsVersion ?? 2;

return next();
});

UserSchema.pre("update", function (next) {
if (this.isModified("password")) {
this.password = bcrypt.hashSync(this.password, saltRounds);
UserSchema.pre(
["updateOne", "findOneAndUpdate", "updateMany", "update", "save"],
function (next) {
if (this.getUpdate) {
const updates = this.getUpdate();
const fieldsToValidate = [
"_id",
"firstName",
"lastName",
"userName",
"email",
"organization",
"long_organization",
"privilege",
"country",
"profilePicture",
"phoneNumber",
"createdAt",
"updatedAt",
"rateLimit",
"lastLogin",
"iat",
];

fieldsToValidate.forEach((field) => {
// Check for empty/null/undefined values
const value = updates[field] || (updates.$set && updates.$set[field]);

if (value === undefined || value === null || value === "") {
return next(
new HttpError("Validation Error", httpStatus.BAD_REQUEST, {
message: `${field} cannot be empty, null, or undefined`,
})
);
}
});
if (updates) {
// Prevent modification of certain immutable fields
const immutableFields = ["firebase_uid", "email", "createdAt", "_id"];

immutableFields.forEach((field) => {
if (updates[field]) delete updates[field];

if (updates.$set && updates.$set[field]) {
return next(
new HttpError(
"Modification Not Allowed",
httpStatus.BAD_REQUEST,
{ message: `Cannot modify ${field} after creation` }
)
);
}

if (updates.$set) delete updates.$set[field];
if (updates.$push) delete updates.$push[field];
});

// Validate network roles and group roles limits
if (
updates.network_roles &&
updates.network_roles.length > ORGANISATIONS_LIMIT
) {
return next(
new HttpError("Validation Error", httpStatus.BAD_REQUEST, {
message: `Maximum ${ORGANISATIONS_LIMIT} network roles allowed`,
})
);
}

if (
updates.group_roles &&
updates.group_roles.length > ORGANISATIONS_LIMIT
) {
return next(
new HttpError("Validation Error", httpStatus.BAD_REQUEST, {
message: `Maximum ${ORGANISATIONS_LIMIT} group roles allowed`,
})
);
}

// Ensure password is hashed if modified
if (updates.password) {
updates.password = bcrypt.hashSync(updates.password, saltRounds);
}
}
}

// Additional checks for new documents
if (this.isNew) {
const requiredFields = ["firstName", "lastName", "email"];
requiredFields.forEach((field) => {
if (this.isModified(field) && !this[field]) {
return next(new Error(`${field} is required`));
}
});
}

next();
}
return next();
});
);

UserSchema.index({ email: 1 }, { unique: true });
UserSchema.index({ userName: 1 }, { unique: true });
Expand Down Expand Up @@ -702,61 +813,18 @@ UserSchema.statics = {
async modify({ filter = {}, update = {} } = {}, next) {
try {
logText("the user modification function........");
let options = { new: true };
const options = { new: true };
const fieldNames = Object.keys(update);
const fieldsString = fieldNames.join(" ");
let modifiedUpdate = update;
modifiedUpdate["$addToSet"] = {};

if (update.password) {
modifiedUpdate.password = bcrypt.hashSync(update.password, saltRounds);
}

if (modifiedUpdate.profilePicture) {
if (!validateProfilePicture(modifiedUpdate.profilePicture)) {
next(
new HttpError("Bad Request Error", httpStatus.BAD_REQUEST, {
message: "Invalid profile picture URL",
})
);
}
}

if (modifiedUpdate.network_roles) {
if (isEmpty(modifiedUpdate.network_roles.network)) {
delete modifiedUpdate.network_roles;
} else {
modifiedUpdate["$addToSet"] = {
network_roles: { $each: modifiedUpdate.network_roles },
};
delete modifiedUpdate.network_roles;
}
}

if (modifiedUpdate.group_roles) {
if (isEmpty(modifiedUpdate.group_roles.group)) {
delete modifiedUpdate.group_roles;
} else {
modifiedUpdate["$addToSet"] = {
group_roles: { $each: modifiedUpdate.group_roles },
};
delete modifiedUpdate.group_roles;
}
}

if (modifiedUpdate.permissions) {
modifiedUpdate["$addToSet"]["permissions"] = {};
modifiedUpdate["$addToSet"]["permissions"]["$each"] =
modifiedUpdate.permissions;
delete modifiedUpdate["permissions"];
}

// Find and update user
const updatedUser = await this.findOneAndUpdate(
filter,
modifiedUpdate,
update,
options
).select(fieldsString);

// Handle update result
if (!isEmpty(updatedUser)) {
const { _id, ...userData } = updatedUser._doc;
return {
Expand All @@ -765,16 +833,17 @@ UserSchema.statics = {
data: userData,
status: httpStatus.OK,
};
} else if (isEmpty(updatedUser)) {
next(
new HttpError("Bad Request Error", httpStatus.BAD_REQUEST, {
message: "user does not exist, please crosscheck",
})
);
}

// User not found
return next(
new HttpError("Bad Request Error", httpStatus.BAD_REQUEST, {
message: "user does not exist, please crosscheck",
})
);
} catch (error) {
logger.error(`🐛🐛 Internal Server Error -- ${error.message}`);
next(
return next(
new HttpError(
"Internal Server Error",
httpStatus.INTERNAL_SERVER_ERROR,
Expand Down

0 comments on commit ffa7c76

Please sign in to comment.