Skip to content
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
80 changes: 80 additions & 0 deletions backend/functions/src/controllers/authorizedQueueController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { Response } from "express";
import { realtimeDb } from "../config/firebaseConfig";
import AuthRequest from "../types/AuthRequest";
import CashierType from "../types/CashierType";
import Counter from "../types/Counter";

export const getAllOpenedStation = async (req: AuthRequest, res: Response) => {
try {
if (!req.user) {
res.status(401).json({ message: "Unauthorized request" });
return;
}
const stationRef = realtimeDb.ref("stations");
const stationSnapshot = await stationRef.get();
const stations = stationSnapshot.val();


if (!stations) {
res.status(200).json({ openedStations: [] });
return;
}


type Station = {
id: string;
name: string;
description: string;
activated: boolean;
type: CashierType;
};

const allOpenedStations: Station[] = Object.entries(stations).map(([stationId, data]) => ({
id: stationId,
...(data as Omit<Station, "id">),
})).filter((station) => station.activated === true);


res.status(200).json({ openedStations: allOpenedStations });
} catch (error) {
res.status(500).json({ message: (error as Error).message });
}
};


export const displayCurrentServing = async (
req: AuthRequest,
res: Response
) => {
try {
if (!req.user) {
res.status(401).json({ message: "Unauthorized request" });
return;
}
const { stationId }: { stationId: string } = req.body;
if (!stationId || stationId === "") {
res.status(400).json({ message: "Station Id is missing" });
return;
}
const countersRef = realtimeDb.ref("counters");
const snapshot = await countersRef
.orderByChild("stationID")
.equalTo(stationId)
.once("value");

if (!snapshot.exists()) {
res
.status(404)
.json({ message: "No counters found for the given stationID" });
}

const counters: Record<string, Counter> = snapshot.val();
const servingCounters = Object.values(counters)
.filter((counter) => counter.serving && counter.serving.trim() !== "")
.map(({ counterNumber, serving }) => ({ counterNumber, serving }));

res.status(200).json({ servingCounters });
} catch (error) {
res.status(500).json({ message: (error as Error).message });
}
};
60 changes: 36 additions & 24 deletions backend/functions/src/controllers/cashierController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const serveCustomer = async (req: AuthRequest, res: Response) => {
}

const customerRef = firestoreDb.collection("queue");
const { customerDocID, customerEmail} = await firestoreDb.runTransaction(
const { customerDocID, customerEmail } = await firestoreDb.runTransaction(
async (transaction) => {
const queueSnapshot = await transaction.get(
customerRef
Expand Down Expand Up @@ -67,20 +67,24 @@ export const serveCustomer = async (req: AuthRequest, res: Response) => {
);
await currentServingRef.set(customerDocID);

if (!req.user) {
res.status(401).json({ message: "User ID is missing!" });
return;
}

// 🔄 Assign Customer to Counter
await counterRef.update({
counterNumber,
stationID,
uid: req.user?.uid,
uid: req.user.uid,
serving: customerDocID, // ✅ Use document ID as queueID
});

if (!req.user) {
res.status(401).json({message: "User ID is missing!"});
return;
}
const receiver = await auth.getUser(req.user.uid);
const displayName = receiver.displayName;

await customerRef.doc(customerDocID).set({ servedAt: Date.now() }, { merge: true });

await recordLog(
req.user.uid,
ActionType.SERVE_CUSTOMER,
Expand All @@ -90,6 +94,8 @@ export const serveCustomer = async (req: AuthRequest, res: Response) => {
await queueCountRef.transaction((currentValue) => {
return currentValue === 1 ? 0 : 1;
});


res.status(200).json({
message: "Customer assigned to cashier",
customer: customerDocID,
Expand All @@ -109,6 +115,10 @@ export const completeTransaction = async (req: AuthRequest, res: Response) => {
counterID,
}: { queueID: string; stationID: string; counterID: string } = req.body;

if (!req.user) {
res.status(401).json({ message: "User ID is missing!" });
return;
}
const queueRef = firestoreDb.collection("queue").doc(queueID);
const queueSnapshot = await queueRef.get();

Expand All @@ -117,9 +127,11 @@ export const completeTransaction = async (req: AuthRequest, res: Response) => {
return;
}

await queueRef.update({
await queueRef.set({
customerStatus: "complete",
});
completedAt: Date.now(),
servedBy: req.user.uid,
}, { merge: true });

const currentServingRef = realtimeDb.ref(
`current-serving/${stationID}/${counterID}`
Expand All @@ -131,10 +143,7 @@ export const completeTransaction = async (req: AuthRequest, res: Response) => {
);
const currentCustomerData = await currentCounterServing.get();
const currentCustomer = currentCustomerData.val();
if (!req.user) {
res.status(401).json({message: "User ID is missing!"});
return;
}

const receiver = await auth.getUser(req.user.uid);
const displayName = receiver.displayName;
await recordLog(
Expand Down Expand Up @@ -243,10 +252,10 @@ export const getCurrentServing = async (req: AuthRequest, res: Response) => {

export const notifyCustomer = async (req: AuthRequest, res: Response) => {
try {
const {counterNumber, queueID}: {counterNumber: string, queueID: string} = req.body;
const { counterNumber, queueID }: { counterNumber: string, queueID: string } = req.body;

if (!counterNumber || !queueID) {
res.status(400).json({message: "Missing Counter Number or QueueID"});
res.status(400).json({ message: "Missing Counter Number or QueueID" });
return;
}
const queueDoc = await firestoreDb.collection("queue").doc(queueID).get();
Expand Down Expand Up @@ -296,14 +305,21 @@ export const skipCustomer = async (req: AuthRequest, res: Response) => {
const queueRef = firestoreDb.collection("queue").doc(queueID);
const queueSnapshot = await queueRef.get();

if (!req.user) {
res.status(401).json({ message: "User ID is missing!" });
return;
}

if (!queueSnapshot.exists) {
res.status(404).json({ message: "Queue entry not found" });
return;
}

await queueRef.update({
await queueRef.set({
customerStatus: "unsuccessful",
});
completedAt: Date.now(),
servedBy: req.user.uid,
}, { merge: true });

const currentServingRef = realtimeDb.ref(
`current-serving/${stationID}/${counterID}`
Expand All @@ -314,10 +330,6 @@ export const skipCustomer = async (req: AuthRequest, res: Response) => {
`counters/${counterID}/serving`
);
await currentCounterServing.remove();
if (!req.user) {
res.status(401).json({message: "User ID is missing!"});
return;
}

const customerRef = firestoreDb.collection("queue").doc(queueID);
const customer = await customerRef.get();
Expand All @@ -337,19 +349,19 @@ export const skipCustomer = async (req: AuthRequest, res: Response) => {
}
};

export const getRemainingPendingCustomerCount = async (req: AuthRequest, res:Response) => {
export const getRemainingPendingCustomerCount = async (req: AuthRequest, res: Response) => {
try {
const {stationID} = req.query;
const { stationID } = req.query;
if (!stationID) {
res.status(400).json({message: "Missing StationID"});
res.status(400).json({ message: "Missing StationID" });
return;
}
const queueRef = firestoreDb.collection("queue")
.where("customerStatus", "==", "pending")
.where("stationID", "==", stationID.toString());

const remainingQueue = await queueRef.get();
res.status(200).json({remainingCustomersCount: remainingQueue.size});
res.status(200).json({ remainingCustomersCount: remainingQueue.size });
} catch (error) {
res.status(500).json({ message: (error as Error).message });
}
Expand Down
44 changes: 37 additions & 7 deletions backend/functions/src/controllers/queueControllers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { sendEmail } from "../utils/sendEmail";
import { recordLog } from "../utils/recordLog";
import { ActionType } from "../types/activityLog";
import { ZodError } from "zod";
import { customerRatingSchema } from "../zod-schemas/customerRating";

const SECRET_KEY = process.env.JWT_SECRET;
const NEUQUEUE_ROOT_URL = process.env.NEUQUEUE_ROOT_URL;
Expand All @@ -37,7 +38,7 @@ export const generateQrCode = async (req: Request, res: Response) => {
}
};

export const getValidJwtForFormAccess = async (req: QueueRequest, res: Response ) => {
export const getValidJwtForFormAccess = async (req: QueueRequest, res: Response) => {
try {
if (!req.token) {
res.status(401).json({ message: "The token is invalid or missing" });
Expand All @@ -59,11 +60,11 @@ export const getValidJwtForFormAccess = async (req: QueueRequest, res: Response
type: "queue-form",
};

const token = jwt.sign(payload, SECRET_KEY, {expiresIn: "10m"});
const token = jwt.sign(payload, SECRET_KEY, { expiresIn: "10m" });
res.status(201).json({ token: token });
} catch (error) {
if (error instanceof TokenExpiredError) {
res.status(401).json({ message: "Token has expired, please sign in again"});
res.status(401).json({ message: "Token has expired, please sign in again" });
} else {
res.status(500).json({ message: (error as Error).message });
}
Expand Down Expand Up @@ -164,7 +165,7 @@ export const addQueue = async (req: QueueRequest, res: Response) => {
}

const queueToken = jwt.sign(
{ queueID: queueIDWithPrefix, stationID, email, type: "queue-status"},
{ queueID: queueIDWithPrefix, stationID, email, type: "queue-status" },
SECRET_KEY,
{ expiresIn: "10h" }
);
Expand Down Expand Up @@ -401,15 +402,15 @@ export const leaveQueue = async (req: QueueRequest, res: Response) => {
};
const { queueID, email, stationID } = decodedToken;
const queueRef = firestoreDb.collection("queue").doc(queueID);
await queueRef.set({customerStatus: "unsuccessful"}, {merge: true});
await queueRef.set({ customerStatus: "unsuccessful" }, { merge: true });
const invalidTokenRef = firestoreDb
.collection("invalid-token")
.doc(req.token);
await invalidTokenRef.set({ email, timestamp: Date.now() });
const station = await realtimeDb.ref(`stations/${stationID}`).get();
// check if the customer is in the current serving
const currentServingRef = realtimeDb.ref(`current-serving/${stationID}`);
type CounterWithID = {[key: string]: Counter};
type CounterWithID = { [key: string]: Counter };
await currentServingRef.transaction((currentServing: CounterWithID) => {
if (!currentServing) return;

Expand All @@ -427,7 +428,7 @@ export const leaveQueue = async (req: QueueRequest, res: Response) => {
const counterSnapshot = await countersRef.once("value");
const counters = counterSnapshot.val();

const countersWithKey: {[key: string]: Counter} = {};
const countersWithKey: { [key: string]: Counter } = {};
Object.keys(counters).forEach((counterKey) => {
const counterData = counters[counterKey] as Counter;
countersWithKey[counterKey] = counterData;
Expand Down Expand Up @@ -735,3 +736,32 @@ export const notifyCurrentlyServing = async (
res.status(500).json({ message: (error as Error).message });
}
};

export const rateCashier = async (req: QueueRequest, res: Response) => {
try {
if (!req.token) {
res.status(400).json({ message: "Missing token" });
return;
}

const parsedBody = customerRatingSchema.parse(req.body);

const ratingRef = firestoreDb.collection("rating").doc(req.token);
const ratings = await ratingRef.get();

if (ratings.exists) {
res.status(403).json({ message: "Feedback already sent" });
return;
}

await ratingRef.set({
...parsedBody,
...(parsedBody.comment ? { comment: parsedBody.comment } : {}),
});

res.status(201).json({ message: "Feedback sent successfully!" });
} catch (error) {
res.status(500).json({ message: (error as Error).message });
}
};

Loading