fix: entity_id now appears in customer products, added test for it
This commit is contained in:
@@ -2,6 +2,13 @@
|
||||
|
||||

|
||||
|
||||

|
||||

|
||||

|
||||
[](https://app.useautumn.com)
|
||||
[](https://docs.useautumn.com)
|
||||
[](https://deepwiki.com/useautumn/autumn)
|
||||
|
||||
[Autumn](https://useautumn.com) is an open-source layer between Stripe and your application, allowing you to create any pricing model and embed it with a couple lines of code. On Autumn you can build:
|
||||
- Subscriptions
|
||||
- Credit systems & top ups
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 690 KiB After Width: | Height: | Size: 1.8 MiB |
@@ -7,7 +7,8 @@ fi
|
||||
|
||||
# Set default subdomain if env var not found
|
||||
if [ -z "$LOCALTUNNEL_RESERVED_KEY" ]; then
|
||||
LOCALTUNNEL_RESERVED_KEY="abcasdjnaslkjdnas"
|
||||
echo "No LOCALTUNNEL_RESERVED_KEY found in .env, exiting..."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "LOCALTUNNEL_RESERVED_KEY: ${LOCALTUNNEL_RESERVED_KEY}"
|
||||
|
||||
1
server/src/external/stripe/stripeWebhooks.ts
vendored
1
server/src/external/stripe/stripeWebhooks.ts
vendored
@@ -195,7 +195,6 @@ stripeWebhookRouter.post(
|
||||
logger,
|
||||
res: response,
|
||||
});
|
||||
return;
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -67,9 +67,6 @@ export async function handleCusDiscountDeleted({
|
||||
return;
|
||||
}
|
||||
|
||||
// Send response first...?
|
||||
res.status(200).json({ message: "OK" });
|
||||
|
||||
const reward = await RewardService.get({
|
||||
db,
|
||||
orgId: org.id,
|
||||
|
||||
@@ -101,7 +101,11 @@ adminRouter.get("/orgs", async (req: any, res: any) => {
|
||||
.where(
|
||||
and(
|
||||
search
|
||||
? or(ilike(organizations.name, `%${search as string}%`))
|
||||
? or(
|
||||
ilike(organizations.name, `%${search as string}%`),
|
||||
ilike(organizations.id, `%${search as string}%`),
|
||||
ilike(organizations.slug, `%${search as string}%`),
|
||||
)
|
||||
: undefined,
|
||||
after
|
||||
? or(
|
||||
@@ -143,7 +147,7 @@ adminRouter.get("/orgs", async (req: any, res: any) => {
|
||||
...org,
|
||||
users: memberships
|
||||
.filter((membership) => membership.member.organizationId === org.id)
|
||||
.map((membership) => membership.user?.email),
|
||||
.map((membership) => membership.user),
|
||||
})),
|
||||
hasNextPage: orgs.length > 20,
|
||||
});
|
||||
|
||||
@@ -413,6 +413,7 @@ export const processFullCusProduct = ({
|
||||
|
||||
stripe_subscription_ids: cusProduct.subscription_ids || [],
|
||||
started_at: cusProduct.starts_at,
|
||||
// entity_id: cusProduct.entity_id,
|
||||
entity_id: cusProduct.internal_entity_id
|
||||
? entities?.find((e) => e.internal_id == cusProduct.internal_entity_id)
|
||||
?.id
|
||||
|
||||
@@ -32,7 +32,8 @@ export const handleGetCustomer = async (req: any, res: any) =>
|
||||
CusProductStatus.PastDue,
|
||||
CusProductStatus.Scheduled,
|
||||
],
|
||||
withEntities: expandArray.includes(CusExpand.Entities),
|
||||
withEntities: true,
|
||||
// withEntities: expandArray.includes(CusExpand.Entities),
|
||||
expand: expandArray,
|
||||
allowNotFound: true,
|
||||
}),
|
||||
|
||||
@@ -11,6 +11,7 @@ import { constructArrearItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { TestFeature } from "tests/setup/v2Features.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js";
|
||||
import { expectProductAttached } from "tests/utils/expectUtils/expectProductAttached.js";
|
||||
|
||||
const testCase = "aentity1";
|
||||
|
||||
@@ -89,5 +90,12 @@ describe(`${chalk.yellowBright(`attach/${testCase}: Testing attach to entity via
|
||||
env,
|
||||
entityId,
|
||||
});
|
||||
|
||||
let customer = await autumn.customers.get(customerId);
|
||||
expectProductAttached({
|
||||
customer,
|
||||
product: pro,
|
||||
entityId,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { Toaster } from "@/components/ui/sonner";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useEnv } from "@/utils/envUtils";
|
||||
import { getRedirectUrl, navigateTo } from "@/utils/genUtils";
|
||||
import { navigateTo } from "@/utils/genUtils";
|
||||
import LoadingScreen from "@/views/general/LoadingScreen";
|
||||
import { MainSidebar } from "@/views/main-sidebar/MainSidebar";
|
||||
import { AppEnv } from "@autumn/shared";
|
||||
import { useEffect } from "react";
|
||||
import { Navigate, Outlet, useLocation, useNavigate } from "react-router";
|
||||
import { Outlet, useNavigate } from "react-router";
|
||||
|
||||
import { usePostHog } from "posthog-js/react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -19,8 +18,6 @@ export function MainLayout() {
|
||||
const env = useEnv();
|
||||
const { data, isPending } = useSession();
|
||||
|
||||
console.log("Session", data, "isPending", isPending);
|
||||
|
||||
const navigate = useNavigate();
|
||||
const posthog = usePostHog();
|
||||
|
||||
|
||||
21
vite/src/views/admin/adminUtils.ts
Normal file
21
vite/src/views/admin/adminUtils.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { authClient } from "@/lib/auth-client";
|
||||
import { toast } from "sonner";
|
||||
|
||||
export const impersonateUser = async (userId: string) => {
|
||||
console.log("impersonating user", userId);
|
||||
try {
|
||||
await authClient.admin.stopImpersonating();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
const res = await authClient.admin.impersonateUser({
|
||||
userId,
|
||||
});
|
||||
|
||||
if (res.error) {
|
||||
toast.error("Something went wrong");
|
||||
return;
|
||||
}
|
||||
|
||||
window.location.reload();
|
||||
};
|
||||
31
vite/src/views/admin/components/ImpersonateBtn.tsx
Normal file
31
vite/src/views/admin/components/ImpersonateBtn.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { impersonateUser } from "../adminUtils";
|
||||
import { User } from "better-auth";
|
||||
import { useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
export const ImpersonateButton = ({ userId }: { userId?: string }) => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
if (!userId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
await impersonateUser(userId);
|
||||
} catch (error: any) {
|
||||
toast.error(`Failed to impersonate user: ${error.message}`);
|
||||
}
|
||||
setLoading(false);
|
||||
}}
|
||||
shimmer={loading}
|
||||
>
|
||||
Impersonate
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
@@ -1,13 +1,15 @@
|
||||
import { ColumnDef, Row } from "@tanstack/react-table";
|
||||
import CopyButton from "@/components/general/CopyButton";
|
||||
import { format } from "date-fns";
|
||||
import { User } from "better-auth";
|
||||
import { ImpersonateButton } from "./components/ImpersonateBtn";
|
||||
|
||||
export type Org = {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
createdAt: string;
|
||||
users: string[];
|
||||
users: User[];
|
||||
};
|
||||
|
||||
// Helper to add width and canCopy to columns
|
||||
@@ -70,13 +72,31 @@ export const columns: OrgColumnDef[] = [
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "impersonate",
|
||||
header: "Impersonate",
|
||||
width: 150,
|
||||
cell: ({ row }: { row: Row<Org> }) => {
|
||||
const users = row.getValue("users") as User[];
|
||||
|
||||
if (!users || users.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <ImpersonateButton userId={users?.[0]?.id} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "users",
|
||||
header: "Users",
|
||||
width: "100%",
|
||||
cell: ({ row }: { row: Row<Org> }) => {
|
||||
const value = row.getValue("users");
|
||||
return <span className="truncate">{(value as string[]).join(", ")}</span>;
|
||||
return (
|
||||
<span className="truncate">
|
||||
{(value as User[]).map((user) => user.email)}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -4,6 +4,8 @@ import CopyButton from "@/components/general/CopyButton";
|
||||
import { format } from "date-fns";
|
||||
import { authClient } from "@/lib/auth-client";
|
||||
import { toast } from "sonner";
|
||||
import { impersonateUser } from "./adminUtils";
|
||||
import { ImpersonateButton } from "./components/ImpersonateBtn";
|
||||
|
||||
export type User = {
|
||||
id: string;
|
||||
@@ -78,25 +80,7 @@ export const columns: UserColumnDef[] = [
|
||||
header: "Impersonate",
|
||||
width: "100%",
|
||||
cell: ({ row }: { row: Row<User> }) => (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={async () => {
|
||||
const res = await authClient.admin.impersonateUser({
|
||||
userId: row.original.id,
|
||||
});
|
||||
|
||||
if (res.error) {
|
||||
toast.error("Something went wrong");
|
||||
return;
|
||||
}
|
||||
|
||||
window.location.reload();
|
||||
}}
|
||||
style={{ width: 100 }}
|
||||
>
|
||||
Impersonate
|
||||
</Button>
|
||||
<ImpersonateButton userId={row.original.id} />
|
||||
),
|
||||
enableSorting: false,
|
||||
enableHiding: false,
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { NavButton } from "./NavButton";
|
||||
import { SidebarTop } from "./SidebarTop";
|
||||
|
||||
import { useEnv } from "@/utils/envUtils";
|
||||
import SidebarBottom from "./SidebarBottom";
|
||||
import { NavButton } from "./NavButton";
|
||||
import { useEnv } from "@/utils/envUtils";
|
||||
import { useState } from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { SidebarContext } from "./SidebarContext";
|
||||
@@ -53,14 +51,6 @@ export const MainSidebar = () => {
|
||||
title="Developer"
|
||||
env={env}
|
||||
/>
|
||||
{/* <AdminOnly>
|
||||
<NavButton
|
||||
value="admin"
|
||||
icon={<Shield size={15} />}
|
||||
title="Admin"
|
||||
env={env}
|
||||
/>
|
||||
</AdminOnly> */}
|
||||
</div>
|
||||
</div>
|
||||
{/* Sidebar bottom */}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { AppEnv } from "@autumn/shared";
|
||||
import { Link } from "react-router";
|
||||
import { useState } from "react";
|
||||
import { useSidebarContext } from "./SidebarContext";
|
||||
import { useEnv } from "@/utils/envUtils";
|
||||
|
||||
export const NavButton = ({
|
||||
value,
|
||||
@@ -18,12 +19,13 @@ export const NavButton = ({
|
||||
value: string;
|
||||
icon: any;
|
||||
title: string;
|
||||
env: AppEnv;
|
||||
env?: AppEnv;
|
||||
className?: string;
|
||||
href?: string;
|
||||
online?: boolean;
|
||||
}) => {
|
||||
// Get window path
|
||||
env = useEnv();
|
||||
const { state } = useSidebarContext();
|
||||
|
||||
const tab = useTab();
|
||||
|
||||
@@ -25,7 +25,14 @@ import {
|
||||
} from "@/lib/auth-client";
|
||||
import { FrontendOrg, user } from "@autumn/shared";
|
||||
import { DropdownMenuGroup } from "@radix-ui/react-dropdown-menu";
|
||||
import { ChevronDown, LogOut, Plus, Settings, Trash } from "lucide-react";
|
||||
import {
|
||||
ChevronDown,
|
||||
LogOut,
|
||||
Plus,
|
||||
Settings,
|
||||
Shield,
|
||||
Trash,
|
||||
} from "lucide-react";
|
||||
import React from "react";
|
||||
import { useState } from "react";
|
||||
import { CreateNewOrg } from "./CreateNewOrg";
|
||||
@@ -38,6 +45,8 @@ import { useMemberships } from "../org-dropdown/hooks/useMemberships";
|
||||
import { useSidebarContext } from "../SidebarContext";
|
||||
import { OrgLogo } from "../org-dropdown/components/OrgLogo";
|
||||
import { AdminHover } from "@/components/general/AdminHover";
|
||||
import { NavButton } from "../NavButton";
|
||||
import { AdminOnly } from "@/views/admin/components/AdminOnly";
|
||||
|
||||
export const OrgDropdown = () => {
|
||||
const { org, isLoading, error } = useOrg();
|
||||
@@ -108,6 +117,19 @@ export const OrgDropdown = () => {
|
||||
align="start"
|
||||
className="border-1 border-zinc-200 shadow-sm w-48"
|
||||
>
|
||||
<AdminOnly>
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
window.location.href = "/admin";
|
||||
}}
|
||||
>
|
||||
<div className="flex justify-between w-full items-center gap-2 text-t2">
|
||||
Admin
|
||||
<Shield size={12} />
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
</AdminOnly>
|
||||
<DropdownMenuItem className="flex justify-between w-full items-center gap-2 text-t2">
|
||||
<div className="flex flex-col">
|
||||
<span>{session?.user?.name}</span>
|
||||
|
||||
Reference in New Issue
Block a user