costco-grocery-list/backend/controllers/group-invites.controller.js
Nico 77ae5be445
All checks were successful
Build & Deploy Costco Grocery List / build (push) Successful in 1m10s
Build & Deploy Costco Grocery List / verify-images (push) Successful in 3s
Build & Deploy Costco Grocery List / deploy (push) Successful in 11s
Build & Deploy Costco Grocery List / notify (push) Successful in 1s
refactor
2026-02-22 01:27:03 -08:00

217 lines
6.2 KiB
JavaScript

const invitesService = require("../services/group-invites.service");
const { sendError } = require("../utils/http");
const { logError } = require("../utils/logger");
const { inviteCodeLast4 } = require("../utils/redaction");
function getClientIp(req) {
const forwardedFor = req.headers["x-forwarded-for"];
if (typeof forwardedFor === "string" && forwardedFor.trim()) {
return forwardedFor.split(",")[0].trim();
}
return req.ip || req.socket?.remoteAddress || null;
}
function parseRequestedGroupId(req) {
const headerGroupId = req.headers["x-group-id"] || req.headers["x-household-id"];
if (headerGroupId) {
const raw = Array.isArray(headerGroupId) ? headerGroupId[0] : headerGroupId;
return raw;
}
if (req.query?.groupId !== undefined) {
return req.query.groupId;
}
if (req.body?.groupId !== undefined) {
return req.body.groupId;
}
return undefined;
}
function clampTtlDays(value) {
const parsed = Number.parseInt(value, 10);
if (!Number.isInteger(parsed)) return 1;
return Math.max(1, Math.min(7, parsed));
}
function mapServiceError(req, res, error, context, extraLog = {}) {
if (error instanceof invitesService.InviteServiceError) {
return sendError(res, error.statusCode, error.message, error.code);
}
logError(req, context, error, extraLog);
return sendError(res, 500, "Failed to process invite request");
}
exports.listInviteLinks = async (req, res) => {
try {
const requestedGroupId = parseRequestedGroupId(req);
const groupId = await invitesService.resolveManagedGroupId(
req.user.id,
requestedGroupId
);
const links = await invitesService.listInviteLinks(req.user.id, groupId);
res.json({ links });
} catch (error) {
return mapServiceError(req, res, error, "groupInvites.listInviteLinks");
}
};
exports.createInviteLink = async (req, res) => {
try {
const requestedGroupId = parseRequestedGroupId(req);
const groupId = await invitesService.resolveManagedGroupId(
req.user.id,
requestedGroupId
);
const ttlDays = clampTtlDays(req.body?.ttlDays);
const expiresAt = new Date(Date.now() + ttlDays * 24 * 60 * 60 * 1000);
const link = await invitesService.createInviteLink(
req.user.id,
groupId,
req.body?.policy,
Boolean(req.body?.singleUse),
expiresAt,
req.request_id,
getClientIp(req),
req.headers["user-agent"] || null
);
res.status(201).json({ link });
} catch (error) {
return mapServiceError(req, res, error, "groupInvites.createInviteLink");
}
};
exports.revokeInviteLink = async (req, res) => {
try {
const requestedGroupId = parseRequestedGroupId(req);
const groupId = await invitesService.resolveManagedGroupId(
req.user.id,
requestedGroupId
);
await invitesService.revokeInviteLink(
req.user.id,
groupId,
req.body?.linkId,
req.request_id,
getClientIp(req),
req.headers["user-agent"] || null
);
res.json({ ok: true });
} catch (error) {
return mapServiceError(req, res, error, "groupInvites.revokeInviteLink");
}
};
exports.reviveInviteLink = async (req, res) => {
try {
const requestedGroupId = parseRequestedGroupId(req);
const groupId = await invitesService.resolveManagedGroupId(
req.user.id,
requestedGroupId
);
const ttlDays = clampTtlDays(req.body?.ttlDays);
const expiresAt = new Date(Date.now() + ttlDays * 24 * 60 * 60 * 1000);
await invitesService.reviveInviteLink(
req.user.id,
groupId,
req.body?.linkId,
expiresAt,
req.request_id,
getClientIp(req),
req.headers["user-agent"] || null
);
res.json({ ok: true });
} catch (error) {
return mapServiceError(req, res, error, "groupInvites.reviveInviteLink");
}
};
exports.deleteInviteLink = async (req, res) => {
try {
const requestedGroupId = parseRequestedGroupId(req);
const groupId = await invitesService.resolveManagedGroupId(
req.user.id,
requestedGroupId
);
await invitesService.deleteInviteLink(
req.user.id,
groupId,
req.body?.linkId,
req.request_id,
getClientIp(req),
req.headers["user-agent"] || null
);
res.json({ ok: true });
} catch (error) {
return mapServiceError(req, res, error, "groupInvites.deleteInviteLink");
}
};
exports.getJoinPolicy = async (req, res) => {
try {
const requestedGroupId = parseRequestedGroupId(req);
const groupId = await invitesService.resolveManagedGroupId(
req.user.id,
requestedGroupId
);
const joinPolicy = await invitesService.getGroupJoinPolicy(req.user.id, groupId);
res.json({ joinPolicy });
} catch (error) {
return mapServiceError(req, res, error, "groupInvites.getJoinPolicy");
}
};
exports.setJoinPolicy = async (req, res) => {
try {
const requestedGroupId = parseRequestedGroupId(req);
const groupId = await invitesService.resolveManagedGroupId(
req.user.id,
requestedGroupId
);
await invitesService.setGroupJoinPolicy(
req.user.id,
groupId,
req.body?.joinPolicy,
req.request_id,
getClientIp(req),
req.headers["user-agent"] || null
);
res.json({ ok: true });
} catch (error) {
return mapServiceError(req, res, error, "groupInvites.setJoinPolicy");
}
};
exports.getInviteLinkSummary = async (req, res) => {
const token = req.params.token;
const inviteLast4 = inviteCodeLast4(token);
try {
const link = await invitesService.getInviteLinkSummaryByToken(
token,
req.user?.id || null
);
res.json({ link });
} catch (error) {
return mapServiceError(req, res, error, "groupInvites.getInviteLinkSummary", {
invite_last4: inviteLast4,
});
}
};
exports.acceptInviteLink = async (req, res) => {
const token = req.params.token;
const inviteLast4 = inviteCodeLast4(token);
try {
const result = await invitesService.acceptInviteLink(
req.user.id,
token,
req.request_id,
getClientIp(req),
req.headers["user-agent"] || null
);
res.json({ result });
} catch (error) {
return mapServiceError(req, res, error, "groupInvites.acceptInviteLink", {
invite_last4: inviteLast4,
});
}
};