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

Get All user status and Delete user status APIs added #2131

Open
wants to merge 6 commits into
base: re-implement-userStatus
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
93 changes: 89 additions & 4 deletions controllers/usersStatus.ts → controllers/newUserStatus.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import { CANCEL_OOO, userState } from "../constants/userStatus";
import { Forbidden, NotFound } from "http-errors";
import {
getUserStatus as getUserStatusFromModel,
updateUserStatus as updateUserStatusFromModel,
updateAllUserStatus as updateAllUserStatusModel,
batchUpdateUsersStatus as batchUpdateUsersStatusModel,
} from "../models/usersStatus";
deleteUserStatus as deleteUserStatusModel,
getAllUserStatus as getAllUserStatusModel,
cancelOooStatus
} from "../models/newUserStatus";
import { getUserIdBasedOnRoute } from "../utils/newUserStatus";
const { INTERNAL_SERVER_ERROR } = require("../constants/errorMessages");

/**
Expand All @@ -14,7 +20,7 @@ const { INTERNAL_SERVER_ERROR } = require("../constants/errorMessages");
*/
const getUserStatus = async (req: any, res: any) => {
try {
let userId: string = req.params.userId;
let userId: string = getUserIdBasedOnRoute(req);
if (userId) {
const userData: any = await getUserStatusFromModel(userId);
const { userStatusExists, id, data } = userData;
Expand Down Expand Up @@ -46,7 +52,7 @@ const getUserStatus = async (req: any, res: any) => {
*/
const updateUserStatus = async (req: any, res: any) => {
try {
let userId: string = req.params.userId;
let userId: string = getUserIdBasedOnRoute(req);
if (userId) {
const dataToUpdate = {
state: "CURRENT",
Expand Down Expand Up @@ -84,6 +90,7 @@ const updateUserStatus = async (req: any, res: any) => {
* @param res {Object} - Express response object
*/
const updateAllUserStatus = async (req, res) => {
console.log("Abc")
try {
const data = await updateAllUserStatusModel();
return res.status(200).json({
Expand Down Expand Up @@ -112,9 +119,87 @@ const batchUpdateUsersStatus = async (req, res) => {
}
};

const deleteUserStatus = async (req, res) => {
try {
const { userId } = req.params;
const deletedUserStatus = await deleteUserStatusModel(userId);
const responseObj = { id: deletedUserStatus.id, userId, message: null };
let statusCode: number;
if (deletedUserStatus.userStatusExisted) {
responseObj.message = "User Status deleted successfully.";
statusCode = 200;
} else {
responseObj.message = "User Status to delete not found.";
statusCode = 404;
}
return res.status(statusCode).json(responseObj);
} catch (error) {
logger.error(`Error while deleting User Status: ${error}`);
return res.boom.badImplementation(INTERNAL_SERVER_ERROR);
}
};

const getAllUserStatus = async (req, res) => {
const limit = parseInt(req.query.size) || 10;
const lastDocId = req.query.next;

try {
const { allUserStatus, lastDocId: nextLastDocId } = await getAllUserStatusModel(req.query, limit, lastDocId);

// Construct the next page URL
const nextPageUrl = allUserStatus.length === limit ? `${req.baseUrl}${req.path}?next=${nextLastDocId}&size=${limit}${req.query.state ? `&state=${req.query.state}` : ''}` : null;

return res.json({
message: "All User Status found successfully.",
totalUserStatus: allUserStatus.length,
pageSize: limit,
nextPageLink: nextPageUrl,
allUserStatus: allUserStatus,
});
} catch (err) {
logger.error(`Error while fetching all the User Status: ${err}`);
return res.boom.badImplementation(INTERNAL_SERVER_ERROR);
}
};

const cancelOOOStatus = async (req, res) => {
let userId: string = req.params.userId;
try {
const responseObject = await cancelOooStatus(userId);
return res.status(200).json(responseObject);
} catch (error) {
logger.error(`Error while cancelling the ${userState.OOO} Status : ${error}`);
if (error instanceof Forbidden) {
return res.status(403).json({
statusCode: 403,
error: "Forbidden",
message: error.message,
});
} else if (error instanceof NotFound) {
return res.status(404).json({
statusCode: 404,
error: "NotFound",
message: error.message,
});
}
return res.boom.badImplementation(INTERNAL_SERVER_ERROR);
}
};

const updateUserStatusController = async (req, res) => {
if (Object.keys(req.body).includes(CANCEL_OOO)) {
await cancelOOOStatus(req, res);
} else {
await updateUserStatus(req, res);
}
};


export default {
getUserStatus,
updateUserStatus,
updateAllUserStatus,
batchUpdateUsersStatus,
deleteUserStatus,
getAllUserStatus,
updateUserStatusController
};
Original file line number Diff line number Diff line change
Expand Up @@ -113,4 +113,28 @@ export const validateMassUpdate = async (req: any, res: CustomResponse, next: Ne
logger.error(`Error validating Query Params for GET ${error.message}`);
res.boom.badRequest(error);
}
};

export const validateGetQueryParams = async (req: any, res: CustomResponse, next: NextFunction) => {
const schema = Joi.object()
.keys({
aggregate: Joi.boolean().valid(true).error(new Error(`Invalid boolean value passed for aggregate.`)),
status: Joi.string()
.trim()
.valid(userState.IDLE, userState.ACTIVE, userState.OOO, userState.ONBOARDING)
.error(new Error(`Invalid State. State must be either IDLE, ACTIVE, OOO, or ONBOARDING`)),
size: Joi.number().optional(),
next: Joi.optional()
})
.messages({
"object.unknown": "Invalid query param provided.",
});

try {
await schema.validateAsync(req.query);
next();
} catch (error) {
logger.error(`Error validating Query Params for GET ${error.message}`);
res.boom.badRequest(error);
}
};
166 changes: 146 additions & 20 deletions models/usersStatus.ts → models/newUserStatus.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import { userState } from "../constants/userStatus";
import { Forbidden, NotFound } from "http-errors";
import firestore from "../utils/firestore";
import { convertTimestampsInUserStatusToUTC, getTomorrowTimeStamp } from "../utils/userStatus";
import { checkIfUserHasLiveTasks, convertTimestampsInUserStatusToUTC, getTomorrowTimeStamp } from "../utils/userStatus";
import admin from "firebase-admin";
const userStatusModel = firestore.collection("userStatus");
const futureStatusModel = firestore.collection("futureStatus");
const memberRoleModel = firestore.collection("member-group-roles");
const usersCollection = firestore.collection("users");
const discordRoleModel = firestore.collection("discord-roles");
const tasksModel = firestore.collection("tasks");
// @ts-ignore
const DISCORD_BASE_URL = config.get("services.discordBot.baseUrl");
import { generateAuthTokenForCloudflare } from "../utils/discord-actions";
import { generateNewStatus } from "../utils/newUserStatus";

const getGroupRole = async (rolename: string) => {
try {
Expand Down Expand Up @@ -108,10 +111,6 @@ const addGroupIdleRoleToDiscordUser = async (userId: string) => {
}
};

/**
* @params userId {string} : id of the user
* @returns {Promise<userStatusModel|Object>} : returns the userStatus of a single user
*/
export const getUserStatus = async (
userId: string
): Promise<{ id: string; data: any; userStatusExists: boolean } | object> => {
Expand Down Expand Up @@ -199,12 +198,73 @@ export const updateUserStatus = async (userId: string, statusData: any) => {
}
};

/**
* @param userId { String }: Id of the User
* @param newStatusData { Object }: Data to be Updated
* @returns Promise<userStatusModel|Object>
*/
// TODO: Fix this implementation
export const cancelOooStatus = async (userId) => {
try {
let userStatusDoc: admin.firestore.QuerySnapshot<admin.firestore.DocumentData>;
let isActive: boolean;
try {
userStatusDoc = await userStatusModel
.where("userId", "==", userId)
.where("state", "==", "CURRENT")
.limit(1)
.get();
} catch (error) {
logger.error(`Unable to fetch user status document from the firestore : ${error.message}`);
throw error;
}
if (!userStatusDoc.size) {
throw new NotFound("No User status document found");
}
const [userStatusDocument] = userStatusDoc.docs;
const docId = userStatusDocument.id;
const docData = userStatusDocument.data();
if (docData.status !== userState.OOO) {
throw new Forbidden(
`The ${userState.OOO} Status cannot be canceled because the current status is ${docData.status}.`
);
}
try {
isActive = await checkIfUserHasLiveTasks(userId, tasksModel);
} catch (error) {
logger.error(`Unable to fetch user status based on the task : ${error.message}`);
throw error;
}
const newStatusData = generateNewStatus(isActive);

const futureStatus = await futureStatusModel
.where("userId", "==", userId)
.where("state", "==", "UPCOMING")
.limit(1)
.get();
if (futureStatus.size) {
const [futureStatusDoc] = futureStatus.docs;
await futureStatusModel.doc(futureStatusDoc.id).update({ state: "NOT_APPLIED" });
}

const today = new Date();
const todaysTime = Date.UTC(
today.getUTCFullYear(),
today.getUTCMonth(),
today.getUTCDate(),
today.getUTCHours(),
today.getUTCMinutes(),
today.getUTCSeconds()
);

const newDocRef = await userStatusModel.add({ userId, ...newStatusData });
await userStatusModel.doc(docId).update({ state: "PAST", endedOn: todaysTime });
if (!isActive) {
await addGroupIdleRoleToDiscordUser(userId);
} else {
await removeGroupIdleRoleFromDiscordUser(userId);
}
return { id: newDocRef.id, userStatusExists: true, data: { userId, ...newStatusData } };
} catch (error) {
logger.error(`Error while canceling ${userState.OOO} status: ${error.message}`);
throw error;
}
};

export const updateAllUserStatus = async () => {
const summary = {
noOfUserStatusUpdated: 0,
Expand All @@ -217,7 +277,7 @@ export const updateAllUserStatus = async () => {
const batch = firestore.batch();
const today = new Date().setUTCHours(0, 0, 0, 0);

const updateUserStatusFromFutureStatus = async (document: any, resolve: (value: unknown)=>void) => {
const updateUserStatusFromFutureStatus = async (document: any, resolve: (value: unknown) => void) => {
const futureStatusData = document.data();
const futureStatusRef = document.ref;
const userId = futureStatusData.userId;
Expand Down Expand Up @@ -273,7 +333,7 @@ export const updateAllUserStatus = async () => {

const promises = userFutureStatusDocs.docs.map((document) => {
return new Promise((resolve, reject) => {
updateUserStatusFromFutureStatus(document, resolve)
updateUserStatusFromFutureStatus(document, resolve);
});
});
await Promise.all(promises);
Expand All @@ -294,7 +354,7 @@ const getNextDayTimeStamp = (timeStamp: number) => {
return nextDateDateTime.getTime();
};

export const batchUpdateUsersStatus = async (users: {userId: string, state: string}[]) => {
export const batchUpdateUsersStatus = async (users: { userId: string; state: string }[]) => {
const batch = firestore.batch();
const summary = {
usersCount: users.length,
Expand Down Expand Up @@ -335,11 +395,8 @@ export const batchUpdateUsersStatus = async (users: {userId: string, state: stri
if (state === userState.IDLE) await addGroupIdleRoleToDiscordUser(userId);
batch.set(newUserStatusRef, newUserStatusData);
} else {
const {
status: currentStatus,
endsOn
} = data;

const { status: currentStatus, endsOn } = data;

if (currentStatus === state) {
currentStatus === userState.ACTIVE ? summary.activeUsersUnaltered++ : summary.idleUsersUnaltered++;
continue;
Expand Down Expand Up @@ -408,4 +465,73 @@ export const batchUpdateUsersStatus = async (users: {userId: string, state: stri
} catch (error) {
throw new Error("Batch operation failed");
}
};
};

export const deleteUserStatus = async (userId: string) => {
try {
const userStatusDocs = await userStatusModel
.where("userId", "==", userId)
.where("state", "==", "CURRENT")
.limit(1)
.get();
const [userStatusDoc] = userStatusDocs.docs;
if (userStatusDoc) {
const today = new Date();
const todaysTime = Date.UTC(
today.getUTCFullYear(),
today.getUTCMonth(),
today.getUTCDate(),
today.getUTCHours(),
today.getUTCMinutes(),
today.getUTCSeconds()
);
const docId = userStatusDoc.id;
await userStatusModel.doc(docId).set({ state: "PAST", endedOn: todaysTime }, { merge: true });
return { id: userStatusDoc.id, userStatusExisted: true, userStatusDeleted: true };
} else {
return { id: null, userStatusExisted: false, userStatusDeleted: false };
}
} catch (error) {
logger.error(`error in deleting User Status Document . Reason - ${error}`);
throw error;
}
};

export const getAllUserStatus = async (query: { status: string }, limit = 10, lastDocId: any) => {
try {
const allUserStatus = [];
let lastDoc = null;

if (lastDocId) {
lastDoc = await userStatusModel.doc(lastDocId).get();
}

let dbQuery = userStatusModel.where("state", "==", "CURRENT");

if (query.status) {
dbQuery = dbQuery.where("status", "==", query.status);
}

if (lastDoc) {
dbQuery = dbQuery.startAfter(lastDoc);
}

const data = await dbQuery.limit(limit).get();
const lastUserStatusDoc = data.docs[data.docs.length - 1];

data.forEach((doc) => {
const currentUserStatus = {
id: doc.id,
userId: doc.data().userId,
status: doc.data().status,
monthlyHours: doc.data().monthlyHours,
};
allUserStatus.push(currentUserStatus);
});

return { allUserStatus, lastDocId: lastUserStatusDoc?.id };
} catch (error) {
logger.error(`error in fetching the User Status of all Users. ${error}`);
throw error;
}
};
2 changes: 1 addition & 1 deletion routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ app.use("/tasks", require("./tasks.js"));
app.use("/taskRequests", require("./taskRequests"));
app.use("/trade", require("./trading"));
app.use("/users/status", require("./userStatus.js"));
app.use("/v1/users/status", require("./usersStatus.ts"));
app.use("/v1/users/status", require("./newUserStatus.ts"));
app.use("/users", require("./users.js"));
app.use("/profileDiffs", require("./profileDiffs.js"));
app.use("/wallet", require("./wallets.js"));
Expand Down
Loading
Loading