Adding api automation workflow

This commit is contained in:
blaisadmin
2026-05-01 00:14:17 -04:00
parent 45fd507eeb
commit 5a3ca9021a
2 changed files with 279 additions and 14 deletions
+94 -2
View File
@@ -176,6 +176,13 @@ const billingIntervalSchema = z.enum(['monthly', 'yearly']);
const integrationTokenScopeSchema = z.enum(['read_only', 'read_write']);
const birdGenderSchema = z.enum(['unknown', 'male', 'female']);
const rescueVerificationStatusSchema = z.enum(['pending', 'approved', 'rejected']);
const rescueOnboardingSchema = z.object({
name: z.string().trim().max(160).optional().or(z.literal('')),
city: z.string().trim().max(120).optional().or(z.literal('')),
state: z.string().trim().max(80).optional().or(z.literal('')),
ein: z.string().trim().max(32).optional().or(z.literal('')),
website: z.string().trim().url().max(2000).optional().or(z.literal('')),
});
const workspaceSchema = z.object({
name: z.string().trim().min(1).max(160),
@@ -183,6 +190,7 @@ const workspaceSchema = z.object({
billingEmail: z.string().trim().email().max(255).optional().or(z.literal('')),
billingPlan: billingPlanSchema.optional(),
billingInterval: billingIntervalSchema.optional(),
rescueOnboarding: rescueOnboardingSchema.optional(),
});
const createWorkspaceSchema = z.object({
@@ -191,6 +199,7 @@ const createWorkspaceSchema = z.object({
billingEmail: z.string().trim().email().max(255).optional().or(z.literal('')),
billingPlan: billingPlanSchema.optional(),
billingInterval: billingIntervalSchema.optional(),
rescueOnboarding: rescueOnboardingSchema.optional(),
});
const workspaceMemberSchema = z.object({
@@ -328,6 +337,7 @@ const smtpPass = process.env.SMTP_PASS?.trim() ?? '';
const smtpFromEmail = process.env.SMTP_FROM_EMAIL?.trim() ?? '';
const smtpFromName = process.env.SMTP_FROM_NAME?.trim() || 'FlockPal';
const rescueStatusNotificationEmail = process.env.RESCUE_STATUS_NOTIFICATION_EMAIL?.trim() || 'appadmin@flockpal.app';
const rescueOnboardingWebhookUrl = 'https://n8n.blaishome.online/webhook/395cd538-5e0d-4e89-8070-9e66f571b7ee';
const stripeSecretKey = process.env.STRIPE_SECRET_KEY?.trim() ?? '';
const stripeWebhookSecret = process.env.STRIPE_WEBHOOK_SECRET?.trim() ?? '';
const withBillingRedirectState = (url: string, billingState: 'success' | 'cancelled' | 'portal') => {
@@ -1044,6 +1054,54 @@ const sendRescueStatusNotification = async ({
return { delivered: true };
};
type RescueOnboardingPayload = z.infer<typeof rescueOnboardingSchema>;
const sendRescueOnboardingWebhook = async ({
action,
workspaceId,
flockName,
ownerEmail,
requestedByUserId,
rescueOnboarding,
}: {
action: 'created' | 'converted';
workspaceId: number;
flockName: string;
ownerEmail: string;
requestedByUserId: string;
rescueOnboarding: RescueOnboardingPayload;
}) => {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 10_000);
try {
const response = await fetch(rescueOnboardingWebhookUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
Name: rescueOnboarding.name,
City: rescueOnboarding.city,
State: rescueOnboarding.state,
EIN: rescueOnboarding.ein,
Website: rescueOnboarding.website,
action,
workspaceId,
flockName,
ownerEmail,
requestedByUserId,
submittedAt: new Date().toISOString(),
}),
signal: controller.signal,
});
if (!response.ok) {
throw new Error(`Rescue onboarding webhook returned ${response.status}`);
}
} finally {
clearTimeout(timeout);
}
};
const issueMagicLinkInvite = async ({
email,
name,
@@ -1937,7 +1995,7 @@ app.get('/api/admin/rescue-workspaces', requireAuth, requireSessionAuth, require
}
});
app.patch('/api/admin/rescue-workspaces/:workspaceId', requireAuth, requireSessionAuth, requireAdmin, async (req: Request, res: Response, next: NextFunction) => {
app.patch('/api/admin/rescue-workspaces/:workspaceId', requireAuth, requireAdmin, requireWriteAccess, async (req: Request, res: Response, next: NextFunction) => {
const parsed = z.object({ rescueVerificationStatus: rescueVerificationStatusSchema }).safeParse(req.body);
if (!parsed.success) {
@@ -2169,6 +2227,23 @@ app.post('/api/workspaces', requireAuth, requireSessionAuth, async (req: Request
try {
const workspaceId = await getNextWorkspaceId();
const billingPlan = resolveBillingPlan(parsed.data.workspaceType, parsed.data.billingPlan);
if (parsed.data.workspaceType === 'rescue') {
if (!parsed.data.rescueOnboarding) {
res.status(400).json({ error: 'Rescue onboarding details are required.' });
return;
}
await sendRescueOnboardingWebhook({
action: 'created',
workspaceId,
flockName: parsed.data.name,
ownerEmail: req.auth!.user.email,
requestedByUserId: req.auth!.user.id,
rescueOnboarding: parsed.data.rescueOnboarding,
});
}
const workspace = await createWorkspace({
id: workspaceId,
name: parsed.data.name,
@@ -2209,6 +2284,7 @@ app.put('/api/workspace', requireAuth, requireSessionAuth, requireWorkspaceRole(
const currentWorkspace = req.auth!.workspace;
const billingPlan = resolveBillingPlan(parsed.data.workspaceType, parsed.data.billingPlan ?? currentWorkspace.billing_plan);
const billingInterval = parsed.data.workspaceType === 'rescue' ? 'monthly' : (parsed.data.billingInterval ?? currentWorkspace.billing_interval);
const isConvertingToRescue = currentWorkspace.workspace_type !== 'rescue' && parsed.data.workspaceType === 'rescue';
const canUpdateWorkspace =
isAdminUser(req.auth!.user) ||
subscriptionAllowsWrite(currentWorkspace) ||
@@ -2225,6 +2301,22 @@ app.put('/api/workspace', requireAuth, requireSessionAuth, requireWorkspaceRole(
return;
}
if (isConvertingToRescue) {
if (!parsed.data.rescueOnboarding) {
res.status(400).json({ error: 'Rescue onboarding details are required.' });
return;
}
await sendRescueOnboardingWebhook({
action: 'converted',
workspaceId: currentWorkspace.id,
flockName: parsed.data.name,
ownerEmail: req.auth!.user.email,
requestedByUserId: req.auth!.user.id,
rescueOnboarding: parsed.data.rescueOnboarding,
});
}
const workspace = await updateWorkspace({
workspaceId: currentWorkspace.id,
name: parsed.data.name,
@@ -2234,7 +2326,7 @@ app.put('/api/workspace', requireAuth, requireSessionAuth, requireWorkspaceRole(
billingInterval,
});
if (workspace?.workspace_type === 'rescue' && currentWorkspace.workspace_type !== 'rescue') {
if (workspace?.workspace_type === 'rescue' && isConvertingToRescue) {
await sendRescueStatusNotification({
workspace,
ownerEmail: req.auth!.user.email,