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
217 lines
6.2 KiB
JavaScript
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,
|
|
});
|
|
}
|
|
};
|