All files userController.ts

100% Statements 54/54
97.14% Branches 34/35
100% Functions 3/3
100% Lines 54/54

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149            1x   1x   4x 4x   4x 1x 1x     3x   1x   2x 2x 1x 1x   1x         5x 5x   5x 1x 1x       4x         3x 1x 1x     2x     2x             2x   2x             2x         2x 1x             2x   1x 1x         6x 6x 6x   6x 1x 1x     5x 5x                   5x         4x                 3x 3x   3x 2x 2x 1x   2x   1x       4x   1x 1x            
import { Request, Response } from "express";
import { signupManualUser } from "@deliverables.org/helpers";
import { getItem, deleteItem, addItem, getByID } from "@deliverables.org/database";
import { v4 as uuidv4 } from "uuid";
import { sendWhatsAppMessage, createNotificationTemplate } from "@deliverables.org/helpers"
 
const TABLE_NAME = "Deliverables";
 
export const userController = {
    manualSignup: async (req: Request, res: Response) => {
        try {
            const { name, phone, complexId, unitNumber, email } = req.body;
 
            if (!name || !phone || !complexId) {
                res.status(400).json({ message: "Missing required fields: name, phone, complexId" });
                return;
            }
 
            const user = await signupManualUser(name, phone, complexId, unitNumber, email);
 
            res.status(201).json({ success: true, user });
        } catch (error: any) {
            console.error("Manual signup error:", error);
            if (error.message === "User with this phone number already exists.") {
                res.status(409).json({ message: error.message });
                return;
            }
            res.status(500).json({ message: "Internal server error" });
        }
    },
 
    deleteUser: async (req: Request, res: Response) => {
        try {
            const { complexId, userId } = req.params;
 
            if (!complexId || !userId) {
                res.status(400).json({ message: "Missing complexId or userId" });
                return;
            }
 
            // 1. Get User to find phone number
            const userItem = await getItem({
                TableName: TABLE_NAME,
                Key: { PK: `COMPLEX#${complexId}`, SK: `USER#${userId}` }
            });
 
            if (!userItem.Item) {
                res.status(404).json({ message: "User not found" });
                return;
            }
 
            const phone = userItem.Item.phone;
 
            // 2. Delete User Item
            await deleteItem({
                TableName: TABLE_NAME,
                Key: { PK: `COMPLEX#${complexId}`, SK: `USER#${userId}` }
            });
 
            // 3. Delete Phone Lookup
            // We now use a unique SK per complex: USER_LOOKUP#{complexId}
            Eif (phone) {
                // Delete the specific lookup for this complex
                await deleteItem({
                    TableName: TABLE_NAME,
                    Key: { PK: `PHONE#${phone}`, SK: `USER_LOOKUP#${complexId}` }
                });
 
                // Optional: Also try to delete the legacy lookup (SK: "USER_LOOKUP") if it points to this user
                // This cleans up old data as we migrate
                const legacyLookup = await getItem({
                    TableName: TABLE_NAME,
                    Key: { PK: `PHONE#${phone}`, SK: "USER_LOOKUP" }
                });
 
                if (legacyLookup.Item && legacyLookup.Item.userId === userId) {
                    await deleteItem({
                        TableName: TABLE_NAME,
                        Key: { PK: `PHONE#${phone}`, SK: "USER_LOOKUP" }
                    });
                }
            }
 
            res.status(200).json({ success: true });
        } catch (error) {
            console.error("Delete user error:", error);
            res.status(500).json({ message: "Internal server error" });
        }
    },
 
    addPackageToUser: async (req: Request, res: Response) => {
        try {
            const userId = req.params.userId as string;
            const { packageData, notify } = req.body;
 
            if (!userId || !packageData) {
                res.status(400).json({ message: "Missing userId or packageData" });
                return;
            }
 
            const packageId = uuidv4();
            const newItem = {
                PK: `USER#${userId}`,
                SK: `PACKAGE#${packageId}`,
                type: 'package',
                id: packageId,
                ...packageData,
                delivered: false,
                timeStamp: new Date().toISOString()
            };
 
            await addItem({
                TableName: TABLE_NAME,
                Item: newItem
            });
 
            if (notify) {
                // We need to find the user's phone number.
                // Since we only have userId, and users are stored under COMPLEX#<complexId>, SK: USER#<userId>
                // We might need to query by GSI if we don't have complexId.
                // However, let's assume we can find the user via GSI or if we pass complexId.
                // Actually, looking at deleteUser, it requires complexId.
                // Let's check if we can get user by ID using getByID helper if it exists or GSI.
                // dbHelper has getByID!
 
                const userResult = await getByID(userId);
                const user = userResult.Items && userResult.Items.length > 0 ? userResult.Items[0] : null;
 
                if (user && user.phone) {
                    let phone = user.phone;
                    if (!phone.startsWith("whatsapp:")) {
                        phone = "whatsapp:" + phone;
                    }
                    await sendWhatsAppMessage(phone, "", createNotificationTemplate(user.name));
                } else {
                    console.warn(`User ${userId} not found or no phone number for notification.`);
                }
            }
 
            res.status(201).json({ success: true, packageId });
        } catch (error) {
            console.error("Add package error:", error);
            res.status(500).json({ message: "Internal server error" });
        }
    }
};
 
export default userController;