deep research addressement

This commit is contained in:
2026-06-01 08:40:10 -04:00
parent c159f07322
commit ba73daa66c
205 changed files with 157390 additions and 951 deletions

View File

@@ -1,4 +1,4 @@
import { object, string, minLength, optional, picklist } from "valibot";
import { object, string, minLength, optional, picklist, boolean } from "valibot";
import { returnUrlSchema } from "~/lib/url-validation";
export const CreateCheckoutSessionSchema = object({
@@ -31,3 +31,17 @@ export const UpgradeFromTrialSchema = object({
plan: picklist(["basic", "plus", "premium"]),
returnUrl: returnUrlSchema,
});
export const CreateTrialSubscriptionSchema = object({
returnUrl: returnUrlSchema,
});
export const ChangeTierSchema = object({
tier: picklist(["basic", "plus", "premium", "family_guard", "family_fortress"]),
});
export const CreateFamilyCheckoutSessionSchema = object({
tier: picklist(["family_guard", "family_fortress"]),
returnUrl: returnUrlSchema,
familyGroupId: string([minLength(1)]),
});

View File

@@ -34,3 +34,7 @@ export const ResolveAlertSchema = object({
alertId: string([minLength(1)]),
resolution: picklist(["RESOLVED", "FALSE_POSITIVE"]),
});
export const FamilyThreatScoreSchema = object({
groupId: string([minLength(1)]),
});

View File

@@ -0,0 +1,65 @@
import { object, string, email, minLength, optional, picklist, array, boolean, number } from "valibot";
export const CreateFamilyGroupSchema = object({
name: string([minLength(1)]),
planTier: optional(picklist(["family_guard", "family_fortress"])),
});
export const InviteFamilyMemberSchema = object({
email: string([email()]),
role: optional(picklist(["admin", "member"]), "member"),
});
export const AcceptInvitationSchema = object({
token: string([minLength(1)]),
});
export const ResendInvitationSchema = object({
invitationId: string([minLength(1)]),
});
export const CancelInvitationSchema = object({
invitationId: string([minLength(1)]),
});
export const RemoveFamilyMemberSchema = object({
userId: string([minLength(1)]),
});
export const LeaveFamilyGroupSchema = object({
groupId: string([minLength(1)]),
});
export const UpdateMemberRoleSchema = object({
userId: string([minLength(1)]),
role: picklist(["owner", "admin", "member"]),
});
export const TransferOwnershipSchema = object({
newOwnerId: string([minLength(1)]),
});
export const ConfigureServicesSchema = object({
userId: string([minLength(1)]),
services: array(object({
service: picklist(["darkwatch", "spamshield", "removebrokers", "hometitle", "voiceprint"]),
enabled: boolean(),
})),
});
export const UpdateAlertPreferencesSchema = object({
groupId: string([minLength(1)]),
preferences: array(object({
alertType: picklist(["critical", "security", "billing", "general"]),
channel: picklist(["email", "push", "sms"]),
enabled: boolean(),
})),
});
export const UpdateFamilyPlanTierSchema = object({
planTier: picklist(["family_guard", "family_fortress"]),
});
export const MemberDetailSchema = object({
userId: string([minLength(1)]),
});

View File

@@ -1,4 +1,4 @@
import { object, string, minLength, optional, number, picklist } from "valibot";
import { object, string, minLength, optional, number, picklist, boolean } from "valibot";
export const PersonalInfoSchema = object({
fullName: string([minLength(1)]),
@@ -35,3 +35,17 @@ export const RemovalRequestsFilterSchema = object({
page: optional(number(), 1),
limit: optional(number(), 20),
});
export const EnableAdapterSchema = object({
brokerId: string([minLength(1)]),
});
export const ReScanConfigSchema = object({
cooldownDays: optional(number(), 7),
batchSize: optional(number(), 50),
autoReSubmit: optional(boolean(), true),
});
export const CostHistorySchema = object({
months: optional(number(), 6),
});

View File

@@ -6,6 +6,7 @@ export const CheckNumberSchema = object({
export const ClassifySMSSchema = object({
text: string([minLength(1)]),
threshold: optional(picklist(["strict", "moderate", "lenient"]), "moderate"),
});
export const ClassifyCallSchema = object({

View File

@@ -19,3 +19,11 @@ export const UpdateRoleSchema = object({
userId: string(),
role: picklist(["owner", "admin", "member"]),
});
export const InviteByEmailSchema = object({
email: string([email()]),
});
export const AcceptInviteSchema = object({
token: string(),
});

View File

@@ -6,6 +6,7 @@ import {
optional,
number,
picklist,
boolean,
} from "valibot";
/**
@@ -29,6 +30,11 @@ export const CreateEnrollmentSchema = object({
audioBase64: string([minLength(1), maxLength(MAX_BASE64_LENGTH)]),
});
export const EnrollAdditionalSampleSchema = object({
enrollmentId: string([minLength(1)]),
audioBase64: string([minLength(1), maxLength(MAX_BASE64_LENGTH)]),
});
export const DeleteEnrollmentSchema = object({
enrollmentId: string([minLength(1)]),
});
@@ -48,6 +54,51 @@ export const AnalysisResultSchema = object({
analysisId: string([minLength(1)]),
});
export const AnalysisFeedbackSchema = object({
analysisId: string([minLength(1)]),
isFalsePositive: boolean(),
notes: optional(string()),
});
export const JobStatusSchema = object({
jobId: string([minLength(1)]),
});
/** Call recording analysis schemas */
export const AnalyzeCallRecordingSchema = object({
/** Audio file as base64 (alternative to multipart upload) */
audioBase64: optional(string([minLength(1), maxLength(MAX_BASE64_LENGTH)])),
/** Phone number of the caller/called party */
phoneNumber: string([minLength(1)]),
/** Call direction */
direction: string(),
/** Call duration in seconds */
duration: number(),
/** Call start timestamp ISO string */
callStartedAt: string(),
/** Two-party consent state detected on device */
consentState: optional(string()),
});
export const GetCallAnalysesSchema = object({
page: optional(number(), 1),
limit: optional(number(), 20),
status: optional(string()),
});
export const GetCallAnalysisSchema = object({
callRecordingId: string([minLength(1)]),
});
export const UpdateCallAnalysisSettingsSchema = object({
callAnalysisEnabled: optional(boolean()),
autoAnalyze: optional(boolean()),
audioRetentionDays: optional(number()),
notifyOnSynthetic: optional(boolean()),
autoBlockSynthetic: optional(boolean()),
});
export const EmergencyHangupSchema = object({
callRecordingId: string([minLength(1)]),
phoneNumber: string([minLength(1)]),
});

View File

@@ -47,17 +47,20 @@ describe("SubscriptionSchema", () => {
status: "active",
current_period_start: 1700000000,
current_period_end: 1702678400,
cancel_at_period_end: "false",
trial_end: 1700500000,
cancel_at_period_end: false,
metadata: { userId: "user_123" },
items: {
data: { price: { id: "price_basic" } },
data: [{ price: { id: "price_basic" } }],
},
};
const result = safeParse(SubscriptionSchema, data);
expect(result.success).toBe(true);
if (result.success) {
expect(result.output.current_period_start).toBe(1700000000);
expect(result.output.items?.data?.price?.id).toBe("price_basic");
expect(result.output.items?.data?.[0]?.price?.id).toBe("price_basic");
expect(result.output.trial_end).toBe(1700500000);
expect(result.output.cancel_at_period_end).toBe(false);
}
});
@@ -75,14 +78,17 @@ describe("SubscriptionSchema", () => {
const result = safeParse(SubscriptionSchema, data);
expect(result.success).toBe(true);
if (result.success) {
expect(result.output.cancel_at_period_end).toBe("false");
expect(result.output.cancel_at_period_end).toBe(false);
}
});
it("accepts string cancel_at_period_end", () => {
const data = { id: "sub_123", cancel_at_period_end: "true" };
it("accepts boolean cancel_at_period_end", () => {
const data = { id: "sub_123", cancel_at_period_end: true };
const result = safeParse(SubscriptionSchema, data);
expect(result.success).toBe(true);
if (result.success) {
expect(result.output.cancel_at_period_end).toBe(true);
}
});
it("rejects missing required id", () => {
@@ -100,6 +106,23 @@ describe("SubscriptionSchema", () => {
const result = safeParse(SubscriptionSchema, data);
expect(result.success).toBe(true);
});
it("handles items as array of price objects", () => {
const data = {
id: "sub_123",
items: {
data: [
{ price: { id: "price_1" } },
{ price: { id: "price_2" } },
],
},
};
const result = safeParse(SubscriptionSchema, data);
expect(result.success).toBe(true);
if (result.success) {
expect(result.output.items?.data).toHaveLength(2);
}
});
});
describe("InvoiceSchema", () => {
@@ -112,6 +135,23 @@ describe("InvoiceSchema", () => {
}
});
it("accepts full invoice data with amount and currency", () => {
const data = {
id: "in_123",
subscription: "sub_456",
amount_due: 1999,
currency: "usd",
status: "paid",
};
const result = safeParse(InvoiceSchema, data);
expect(result.success).toBe(true);
if (result.success) {
expect(result.output.id).toBe("in_123");
expect(result.output.amount_due).toBe(1999);
expect(result.output.currency).toBe("usd");
}
});
it("accepts invoice without subscription (for partial invoices)", () => {
const data = { id: "in_123" };
const result = safeParse(InvoiceSchema, data);

View File

@@ -1,4 +1,4 @@
import { object, string, optional, number, type Output } from "valibot";
import { object, string, optional, number, array, boolean, type Output } from "valibot";
/**
* Validates a Stripe Checkout Session object from webhook data.
@@ -16,10 +16,12 @@ export const CheckoutSessionSchema = object({
/**
* Price item inside a Stripe Subscription.
*/
const PriceItemSchema = object({
price: object({
id: string(),
}),
const PriceObjectSchema = object({
id: string(),
});
const SubscriptionItemSchema = object({
price: optional(PriceObjectSchema),
});
/**
@@ -30,7 +32,8 @@ export const SubscriptionSchema = object({
status: optional(string()),
current_period_start: optional(number()),
current_period_end: optional(number()),
cancel_at_period_end: optional(string(), "false"),
trial_end: optional(number()),
cancel_at_period_end: optional(boolean(), false),
metadata: optional(
object({
userId: optional(string()),
@@ -38,7 +41,7 @@ export const SubscriptionSchema = object({
),
items: optional(
object({
data: optional(PriceItemSchema),
data: optional(array(SubscriptionItemSchema), []),
}),
),
});
@@ -47,7 +50,11 @@ export const SubscriptionSchema = object({
* Validates a Stripe Invoice object from webhook data.
*/
export const InvoiceSchema = object({
id: optional(string()),
subscription: optional(string()),
amount_due: optional(number()),
currency: optional(string()),
status: optional(string()),
});
// Type exports for use in billing.service.ts