chore: moved org routes over to hono
This commit is contained in:
28
server/src/honoMiddlewares/adminAuthMiddleware.ts
Normal file
28
server/src/honoMiddlewares/adminAuthMiddleware.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { ErrCode, RecaseError } from "@autumn/shared";
|
||||
import type { Context, Next } from "hono";
|
||||
import type { HonoEnv } from "@/honoUtils/HonoEnv.js";
|
||||
import { auth } from "@/utils/auth.js";
|
||||
import { ADMIN_USER_IDs } from "@/utils/constants.js";
|
||||
|
||||
/**
|
||||
* Admin auth middleware for Hono
|
||||
* Validates that the user is an admin user
|
||||
*/
|
||||
export const adminAuthMiddleware = async (c: Context<HonoEnv>, next: Next) => {
|
||||
const data = await auth.api.getSession({
|
||||
headers: c.req.raw.headers,
|
||||
});
|
||||
|
||||
if (
|
||||
!ADMIN_USER_IDs.includes(data?.session?.userId || "") &&
|
||||
!ADMIN_USER_IDs.includes(data?.session?.impersonatedBy || "")
|
||||
) {
|
||||
throw new RecaseError({
|
||||
message: "Method not allowed",
|
||||
code: ErrCode.InvalidRequest,
|
||||
statusCode: 403,
|
||||
});
|
||||
}
|
||||
|
||||
await next();
|
||||
};
|
||||
@@ -1,13 +1,11 @@
|
||||
import { getRequestListener } from "@hono/node-server";
|
||||
import { type Context, Hono } from "hono";
|
||||
import { Hono } from "hono";
|
||||
import { cors } from "hono/cors";
|
||||
import type { ContentfulStatusCode } from "hono/utils/http-status";
|
||||
import { vercelWebhookRouter } from "./external/vercel/vercelWebhookRouter.js";
|
||||
import { handleConnectWebhook } from "./external/webhooks/connectWebhookRouter.js";
|
||||
import { analyticsMiddleware } from "./honoMiddlewares/analyticsMiddleware.js";
|
||||
import { apiVersionMiddleware } from "./honoMiddlewares/apiVersionMiddleware.js";
|
||||
import { baseMiddleware } from "./honoMiddlewares/baseMiddleware.js";
|
||||
import { betterAuthMiddleware } from "./honoMiddlewares/betterAuthMiddleware.js";
|
||||
import { errorMiddleware } from "./honoMiddlewares/errorMiddleware.js";
|
||||
import { orgConfigMiddleware } from "./honoMiddlewares/orgConfigMiddleware.js";
|
||||
import { queryMiddleware } from "./honoMiddlewares/queryMiddleware.js";
|
||||
@@ -19,18 +17,17 @@ import { handleHealthCheck } from "./honoUtils/handleHealthCheck.js";
|
||||
import { balancesRouter } from "./internal/balances/balancesRouter.js";
|
||||
import { billingRouter } from "./internal/billing/billingRouter.js";
|
||||
import { cusRouter } from "./internal/customers/cusRouter.js";
|
||||
import { internalCusRouter } from "./internal/customers/internalCusRouter.js";
|
||||
import { entityRouter } from "./internal/entities/entityRouter.js";
|
||||
import { featureRouter } from "./internal/features/featureRouter.js";
|
||||
import { handleOAuthCallback } from "./internal/orgs/handlers/stripeHandlers/handleOAuthCallback.js";
|
||||
import { honoOrgRouter } from "./internal/orgs/orgRouter.js";
|
||||
import { platformBetaRouter } from "./internal/platform/platformBeta/platformBetaRouter.js";
|
||||
import { internalProductRouter } from "./internal/products/internalProductRouter.js";
|
||||
import {
|
||||
honoProductBetaRouter,
|
||||
honoProductRouter,
|
||||
migrationRouter,
|
||||
} from "./internal/products/productRouter.js";
|
||||
import { internalRouter } from "./routers/internalRouter.js";
|
||||
import { auth } from "./utils/auth.js";
|
||||
|
||||
const ALLOWED_ORIGINS = [
|
||||
@@ -112,12 +109,6 @@ export const createHonoApp = () => {
|
||||
app.use("/v1/*", refreshCacheMiddleware);
|
||||
app.use("/v1/*", queryMiddleware());
|
||||
|
||||
// General org rate limiter for all other /v1/* routes
|
||||
// app.use("/v1/*", generalRateLimiter);
|
||||
app.get("/v1/test-incident", (c: Context<HonoEnv>) => {
|
||||
return c.json({ message: "Hello, world!" }, 520 as ContentfulStatusCode);
|
||||
});
|
||||
|
||||
app.route("v1", billingRouter);
|
||||
app.route("v1", balancesRouter);
|
||||
app.route("v1", migrationRouter);
|
||||
@@ -134,11 +125,7 @@ export const createHonoApp = () => {
|
||||
app.route("v1/platform/beta", platformBetaRouter);
|
||||
app.route("v1/organization", honoOrgRouter);
|
||||
|
||||
// Internal/dashboard routes - use betterAuthMiddleware for session auth
|
||||
app.use("/products/*", betterAuthMiddleware);
|
||||
app.route("/products", internalProductRouter);
|
||||
app.use("/customers/*", betterAuthMiddleware);
|
||||
app.route("/customers", internalCusRouter);
|
||||
app.route("", internalRouter);
|
||||
|
||||
app.onError(errorMiddleware);
|
||||
|
||||
|
||||
@@ -1,164 +1,9 @@
|
||||
import { member, organizations, user } from "@autumn/shared";
|
||||
import { and, desc, eq, gt, gte, ilike, inArray, lt, or } from "drizzle-orm";
|
||||
import { Router } from "express";
|
||||
import { handleFrontendReqError } from "@/utils/errorUtils.js";
|
||||
import type { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
import { Hono } from "hono";
|
||||
import type { HonoEnv } from "../../honoUtils/HonoEnv";
|
||||
import { handleListAdminOrgs } from "./handleListAdminOrgs";
|
||||
import { handleListAdminUsers } from "./handleListAdminUsers";
|
||||
|
||||
export const adminRouter: Router = Router();
|
||||
export const honoAdminRouter = new Hono<HonoEnv>();
|
||||
|
||||
// adminRouter.post()
|
||||
|
||||
adminRouter.get("/users", async (req: any, res: any) => {
|
||||
try {
|
||||
const { db } = req as ExtendedRequest;
|
||||
|
||||
let { search, after, before } = req.query;
|
||||
|
||||
if (after) {
|
||||
after = {
|
||||
id: after.split(",")[0],
|
||||
createdAt: new Date(after.split(",")[1]),
|
||||
};
|
||||
} else if (before) {
|
||||
before = {
|
||||
id: before.split(",")[0],
|
||||
createdAt: new Date(before.split(",")[1]),
|
||||
};
|
||||
}
|
||||
|
||||
const users = await db
|
||||
.select()
|
||||
.from(user)
|
||||
.where(
|
||||
and(
|
||||
search
|
||||
? or(
|
||||
ilike(user.email, `%${search as string}%`),
|
||||
ilike(user.name, `%${search as string}%`),
|
||||
ilike(user.id, `%${search as string}%`),
|
||||
)
|
||||
: undefined,
|
||||
after
|
||||
? or(
|
||||
lt(user.createdAt, after.createdAt),
|
||||
or(
|
||||
and(
|
||||
eq(user.createdAt, after.createdAt),
|
||||
lt(user.id, after.id),
|
||||
),
|
||||
),
|
||||
)
|
||||
: undefined,
|
||||
before
|
||||
? or(
|
||||
gte(user.createdAt, before.createdAt),
|
||||
or(
|
||||
and(
|
||||
eq(user.createdAt, before.createdAt),
|
||||
gt(user.id, before.id),
|
||||
),
|
||||
),
|
||||
)
|
||||
: undefined,
|
||||
),
|
||||
)
|
||||
.orderBy(desc(user.createdAt), desc(user.id))
|
||||
.limit(21);
|
||||
|
||||
res.json({
|
||||
rows: users.slice(0, 20),
|
||||
hasNextPage: users.length > 20,
|
||||
});
|
||||
} catch (error) {
|
||||
handleFrontendReqError({
|
||||
res,
|
||||
req,
|
||||
error,
|
||||
action: "admin: search users",
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
adminRouter.get("/orgs", async (req: any, res: any) => {
|
||||
try {
|
||||
const { db } = req as ExtendedRequest;
|
||||
|
||||
let { search, after, before } = req.query;
|
||||
|
||||
if (after) {
|
||||
after = {
|
||||
id: after.split(",")[0],
|
||||
createdAt: new Date(after.split(",")[1]),
|
||||
};
|
||||
} else if (before) {
|
||||
before = {
|
||||
id: before.split(",")[0],
|
||||
createdAt: new Date(before.split(",")[1]),
|
||||
};
|
||||
}
|
||||
|
||||
const orgs = await db
|
||||
.select()
|
||||
.from(organizations)
|
||||
.where(
|
||||
and(
|
||||
search
|
||||
? or(
|
||||
ilike(organizations.name, `%${search as string}%`),
|
||||
ilike(organizations.id, `%${search as string}%`),
|
||||
ilike(organizations.slug, `%${search as string}%`),
|
||||
)
|
||||
: undefined,
|
||||
after
|
||||
? or(
|
||||
lt(organizations.createdAt, after.createdAt),
|
||||
or(
|
||||
and(
|
||||
eq(organizations.createdAt, after.createdAt),
|
||||
lt(organizations.id, after.id),
|
||||
),
|
||||
),
|
||||
)
|
||||
: undefined,
|
||||
before
|
||||
? or(
|
||||
gte(organizations.createdAt, before.createdAt),
|
||||
or(
|
||||
and(
|
||||
eq(organizations.createdAt, before.createdAt),
|
||||
gt(organizations.id, before.id),
|
||||
),
|
||||
),
|
||||
)
|
||||
: undefined,
|
||||
),
|
||||
)
|
||||
.orderBy(desc(organizations.createdAt), desc(organizations.id))
|
||||
.limit(21);
|
||||
|
||||
const orgIds = orgs.map((org) => org.id);
|
||||
|
||||
const memberships = await db
|
||||
.select()
|
||||
.from(member)
|
||||
.leftJoin(user, eq(member.userId, user.id))
|
||||
.where(inArray(member.organizationId, orgIds));
|
||||
|
||||
res.json({
|
||||
rows: orgs.slice(0, 20).map((org) => ({
|
||||
...org,
|
||||
users: memberships
|
||||
.filter((membership) => membership.member.organizationId === org.id)
|
||||
.map((membership) => membership.user),
|
||||
})),
|
||||
hasNextPage: orgs.length > 20,
|
||||
});
|
||||
} catch (error) {
|
||||
handleFrontendReqError({
|
||||
res,
|
||||
req,
|
||||
error,
|
||||
action: "admin: search orgs",
|
||||
});
|
||||
}
|
||||
});
|
||||
honoAdminRouter.get("/users", ...handleListAdminUsers);
|
||||
honoAdminRouter.get("/orgs", ...handleListAdminOrgs);
|
||||
|
||||
89
server/src/internal/admin/handleListAdminOrgs.ts
Normal file
89
server/src/internal/admin/handleListAdminOrgs.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import { member, organizations, user } from "@autumn/shared";
|
||||
import { and, desc, eq, gt, gte, ilike, inArray, lt, or } from "drizzle-orm";
|
||||
import { createRoute } from "../../honoMiddlewares/routeHandler";
|
||||
|
||||
export const handleListAdminOrgs = createRoute({
|
||||
handler: async (c) => {
|
||||
const ctx = c.get("ctx");
|
||||
const { db } = ctx;
|
||||
|
||||
const { search, after: afterQuery, before: beforeQuery } = c.req.query();
|
||||
|
||||
let after,
|
||||
before:
|
||||
| {
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
}
|
||||
| undefined;
|
||||
|
||||
if (afterQuery) {
|
||||
after = {
|
||||
id: afterQuery.split(",")[0],
|
||||
createdAt: new Date(afterQuery.split(",")[1]),
|
||||
};
|
||||
} else if (beforeQuery) {
|
||||
before = {
|
||||
id: beforeQuery.split(",")[0],
|
||||
createdAt: new Date(beforeQuery.split(",")[1]),
|
||||
};
|
||||
}
|
||||
|
||||
const orgs = await db
|
||||
.select()
|
||||
.from(organizations)
|
||||
.where(
|
||||
and(
|
||||
search
|
||||
? or(
|
||||
ilike(organizations.name, `%${search as string}%`),
|
||||
ilike(organizations.id, `%${search as string}%`),
|
||||
ilike(organizations.slug, `%${search as string}%`),
|
||||
)
|
||||
: undefined,
|
||||
after
|
||||
? or(
|
||||
lt(organizations.createdAt, after.createdAt),
|
||||
or(
|
||||
and(
|
||||
eq(organizations.createdAt, after.createdAt),
|
||||
lt(organizations.id, after.id),
|
||||
),
|
||||
),
|
||||
)
|
||||
: undefined,
|
||||
before
|
||||
? or(
|
||||
gte(organizations.createdAt, before.createdAt),
|
||||
or(
|
||||
and(
|
||||
eq(organizations.createdAt, before.createdAt),
|
||||
gt(organizations.id, before.id),
|
||||
),
|
||||
),
|
||||
)
|
||||
: undefined,
|
||||
),
|
||||
)
|
||||
.orderBy(desc(organizations.createdAt), desc(organizations.id))
|
||||
.limit(21);
|
||||
|
||||
const orgIds = orgs.map((org) => org.id);
|
||||
|
||||
const memberships = await db
|
||||
.select()
|
||||
.from(member)
|
||||
.leftJoin(user, eq(member.userId, user.id))
|
||||
.where(inArray(member.organizationId, orgIds));
|
||||
|
||||
return c.json({
|
||||
rows: orgs.slice(0, 20).map((org) => ({
|
||||
...org,
|
||||
users: memberships
|
||||
.filter((membership) => membership.member.organizationId === org.id)
|
||||
.map((membership) => membership.user),
|
||||
})),
|
||||
hasNextPage: orgs.length > 20,
|
||||
});
|
||||
},
|
||||
});
|
||||
76
server/src/internal/admin/handleListAdminUsers.ts
Normal file
76
server/src/internal/admin/handleListAdminUsers.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { user } from "@autumn/shared";
|
||||
import { and, desc, eq, gt, gte, ilike, lt, or } from "drizzle-orm";
|
||||
import { createRoute } from "../../honoMiddlewares/routeHandler";
|
||||
|
||||
export const handleListAdminUsers = createRoute({
|
||||
handler: async (c) => {
|
||||
const ctx = c.get("ctx");
|
||||
const { db } = ctx;
|
||||
|
||||
const { search, after: afterQuery, before: beforeQuery } = c.req.query();
|
||||
|
||||
let after,
|
||||
before:
|
||||
| {
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
}
|
||||
| undefined;
|
||||
|
||||
if (afterQuery) {
|
||||
after = {
|
||||
id: afterQuery.split(",")[0],
|
||||
createdAt: new Date(afterQuery.split(",")[1]),
|
||||
};
|
||||
} else if (beforeQuery) {
|
||||
before = {
|
||||
id: beforeQuery.split(",")[0],
|
||||
createdAt: new Date(beforeQuery.split(",")[1]),
|
||||
};
|
||||
}
|
||||
|
||||
const users = await db
|
||||
.select()
|
||||
.from(user)
|
||||
.where(
|
||||
and(
|
||||
search
|
||||
? or(
|
||||
ilike(user.email, `%${search as string}%`),
|
||||
ilike(user.name, `%${search as string}%`),
|
||||
ilike(user.id, `%${search as string}%`),
|
||||
)
|
||||
: undefined,
|
||||
after
|
||||
? or(
|
||||
lt(user.createdAt, after.createdAt),
|
||||
or(
|
||||
and(
|
||||
eq(user.createdAt, after.createdAt),
|
||||
lt(user.id, after.id),
|
||||
),
|
||||
),
|
||||
)
|
||||
: undefined,
|
||||
before
|
||||
? or(
|
||||
gte(user.createdAt, before.createdAt),
|
||||
or(
|
||||
and(
|
||||
eq(user.createdAt, before.createdAt),
|
||||
gt(user.id, before.id),
|
||||
),
|
||||
),
|
||||
)
|
||||
: undefined,
|
||||
),
|
||||
)
|
||||
.orderBy(desc(user.createdAt), desc(user.id))
|
||||
.limit(21);
|
||||
|
||||
return c.json({
|
||||
rows: users.slice(0, 20),
|
||||
hasNextPage: users.length > 20,
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -8,7 +8,6 @@ import { analyticsRouter } from "../analytics/analyticsRouter.js";
|
||||
import { attachRouter } from "../customers/attach/attachRouter.js";
|
||||
import cancelRouter from "../customers/cancel/cancelRouter.js";
|
||||
import { expressCusRouter } from "../customers/cusRouter.js";
|
||||
import { handleGetOrg } from "../orgs/handlers/handleGetOrg.js";
|
||||
import { platformRouter } from "../platform/platformLegacy/platformRouter.js";
|
||||
import { expressProductRouter } from "../products/productRouter.js";
|
||||
import { componentRouter } from "./components/componentRouter.js";
|
||||
@@ -44,11 +43,6 @@ apiRouter.use("/platform", platformRouter);
|
||||
apiRouter.use("/products", expressProductRouter);
|
||||
apiRouter.use("/customers", expressCusRouter);
|
||||
|
||||
// // Used for tests...
|
||||
// apiRouter.post("/organization/stripe", ...handleConnectStripe);
|
||||
// apiRouter.delete("/organization/stripe", ...handleDeleteStripe);
|
||||
apiRouter.get("/organization", handleGetOrg);
|
||||
|
||||
export { apiRouter };
|
||||
|
||||
// Features
|
||||
|
||||
@@ -5,30 +5,26 @@ import { autumnHandler } from "autumn-js/express";
|
||||
import { Router } from "express";
|
||||
import rateLimit from "express-rate-limit";
|
||||
import { createStripeCli } from "@/external/connect/createStripeCli.js";
|
||||
import { withAuth, withOrgAuth } from "../middleware/authMiddleware.js";
|
||||
import { adminRouter } from "./admin/adminRouter.js";
|
||||
import { withAdminAuth } from "./admin/withAdminAuth.js";
|
||||
import { withOrgAuth } from "../middleware/authMiddleware.js";
|
||||
import { analyticsRouter } from "./analytics/internalAnalyticsRouter.js";
|
||||
import { trmnlRouter } from "./api/trmnl/trmnlRouter.js";
|
||||
import { cusRouter } from "./customers/internalCusRouter.js";
|
||||
import { devRouter } from "./dev/devRouter.js";
|
||||
import { InvoiceService } from "./invoices/InvoiceService.js";
|
||||
import { onboardingRouter } from "./orgs/onboarding/onboardingRouter.js";
|
||||
import { orgRouter } from "./orgs/orgRouter.js";
|
||||
|
||||
import { expressProductRouter } from "./products/internalProductRouter.js";
|
||||
import { viewsRouter } from "./saved-views/savedViewsRouter.js";
|
||||
import { userRouter } from "./users/userRouter.js";
|
||||
|
||||
const mainRouter: Router = Router();
|
||||
|
||||
mainRouter.get("", async (req: any, res) => {
|
||||
res.status(200).json({ message: "Hello World" });
|
||||
});
|
||||
// mainRouter.get("", async (req: any, res) => {
|
||||
// res.status(200).json({ message: "Hello World" });
|
||||
// });
|
||||
|
||||
mainRouter.use("/admin", withAdminAuth, adminRouter);
|
||||
mainRouter.use("/users", withAuth, userRouter);
|
||||
// mainRouter.use("/users", withAuth, userRouter);
|
||||
mainRouter.use("/onboarding", withOrgAuth, onboardingRouter);
|
||||
mainRouter.use("/organization", withOrgAuth, orgRouter);
|
||||
|
||||
mainRouter.use("/products", withOrgAuth, expressProductRouter);
|
||||
mainRouter.use("/dev", devRouter);
|
||||
mainRouter.use("/customers", withOrgAuth, cusRouter);
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import { AppEnv, customers, ErrCode, type Organization } from "@autumn/shared";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import type { Response } from "express";
|
||||
import {
|
||||
deauthorizeAccount,
|
||||
deleteConnectedAccount,
|
||||
} from "@/external/connect/connectUtils.js";
|
||||
import type { Logger } from "@/external/logtail/logtailUtils.js";
|
||||
import { deleteSvixApp } from "@/external/svix/svixHelpers.js";
|
||||
import RecaseError, { handleFrontendReqError } from "@/utils/errorUtils.js";
|
||||
import type { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
import { deleteStripeWebhook } from "../orgUtils.js";
|
||||
import RecaseError from "@/utils/errorUtils.js";
|
||||
import { createRoute } from "../../../../honoMiddlewares/routeHandler.js";
|
||||
import { deleteStripeWebhook } from "../../orgUtils.js";
|
||||
|
||||
const deleteSvixWebhooks = async ({
|
||||
org,
|
||||
@@ -98,9 +97,10 @@ export const deleteStripeAccounts = async ({
|
||||
}
|
||||
};
|
||||
|
||||
export const handleDeleteOrg = async (req: ExtendedRequest, res: Response) => {
|
||||
try {
|
||||
const { org, db, logger } = req;
|
||||
export const handleDeleteOrg = createRoute({
|
||||
handler: async (c) => {
|
||||
const ctx = c.get("ctx");
|
||||
const { org, db, logger } = ctx;
|
||||
|
||||
// 1. Check if any customers
|
||||
const hasCustomers = await db.query.customers.findFirst({
|
||||
@@ -134,15 +134,8 @@ export const handleDeleteOrg = async (req: ExtendedRequest, res: Response) => {
|
||||
and(eq(customers.org_id, org.id), eq(customers.env, AppEnv.Sandbox)),
|
||||
);
|
||||
|
||||
res.status(200).json({
|
||||
return c.json({
|
||||
message: "Org deleted",
|
||||
});
|
||||
} catch (error) {
|
||||
handleFrontendReqError({
|
||||
res,
|
||||
error,
|
||||
req,
|
||||
action: "delete-org",
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
import { createRoute } from "../../../../honoMiddlewares/routeHandler.js";
|
||||
import { createOrgResponse } from "../../orgUtils.js";
|
||||
|
||||
export const handleGetOrg = createRoute({
|
||||
handler: async (c) => {
|
||||
const ctx = c.get("ctx");
|
||||
const { org, env } = ctx;
|
||||
return c.json(createOrgResponse({ org, env }));
|
||||
},
|
||||
});
|
||||
@@ -1,61 +0,0 @@
|
||||
import { invitation, user as userTable } from "@autumn/shared";
|
||||
import { and, eq, gt } from "drizzle-orm";
|
||||
import type {
|
||||
ExtendedRequest,
|
||||
ExtendedResponse,
|
||||
} from "@/utils/models/Request.js";
|
||||
|
||||
export const handleGetInvites = async (
|
||||
req: ExtendedRequest,
|
||||
res: ExtendedResponse,
|
||||
) => {
|
||||
try {
|
||||
const { userId, db } = req;
|
||||
|
||||
const user = await db.query.user.findFirst({
|
||||
where: eq(userTable.id, userId ?? ""),
|
||||
});
|
||||
|
||||
const invites = await db.query.invitation.findMany({
|
||||
where: and(
|
||||
eq(invitation.status, "pending"),
|
||||
eq(invitation.email, user?.email ?? ""),
|
||||
gt(invitation.expiresAt, new Date()),
|
||||
),
|
||||
with: {
|
||||
inviter: true,
|
||||
organization: true,
|
||||
},
|
||||
});
|
||||
|
||||
// const joinRequests = await db
|
||||
// .select({
|
||||
// id: orgJoinRequests.id,
|
||||
// organizationId: orgJoinRequests.organizationId,
|
||||
// organizationName: organizations.name,
|
||||
// role: orgJoinRequests.role,
|
||||
// status: orgJoinRequests.status,
|
||||
// createdAt: orgJoinRequests.createdAt,
|
||||
// inviterName: user.name,
|
||||
// inviterEmail: user.email,
|
||||
// })
|
||||
// .from(orgJoinRequests)
|
||||
// .innerJoin(
|
||||
// organizations,
|
||||
// eq(orgJoinRequests.organizationId, organizations.id)
|
||||
// )
|
||||
// .innerJoin(user, eq(orgJoinRequests.inviterId, user.id))
|
||||
// .where(
|
||||
// and(
|
||||
// eq(orgJoinRequests.userId, userId),
|
||||
// eq(orgJoinRequests.status, "pending")
|
||||
// )
|
||||
// )
|
||||
// .orderBy(orgJoinRequests.createdAt);
|
||||
|
||||
res.status(200).json({ invites });
|
||||
} catch (error) {
|
||||
console.error("Error fetching join requests:", error);
|
||||
res.status(500).json({ message: "Internal server error" });
|
||||
}
|
||||
};
|
||||
@@ -1,26 +0,0 @@
|
||||
import type {
|
||||
ExtendedRequest,
|
||||
ExtendedResponse,
|
||||
} from "@/utils/models/Request.js";
|
||||
import { routeHandler } from "@/utils/routerUtils.js";
|
||||
import { OrgService } from "../OrgService.js";
|
||||
import { createOrgResponse } from "../orgUtils.js";
|
||||
|
||||
export const handleGetOrg = async (req: any, res: any) =>
|
||||
routeHandler({
|
||||
req,
|
||||
res,
|
||||
action: "get org",
|
||||
handler: async (req: ExtendedRequest, res: ExtendedResponse) => {
|
||||
if (!req.orgId) {
|
||||
res.status(400).json({
|
||||
message: "Missing orgId",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const org = await OrgService.getFromReq(req);
|
||||
|
||||
res.status(200).json(createOrgResponse({ org, env: req.env }));
|
||||
},
|
||||
});
|
||||
@@ -1,98 +0,0 @@
|
||||
import { session as authSession, member } from "@autumn/shared";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import { handleFrontendReqError } from "@/utils/errorUtils.js";
|
||||
import { OrgService } from "../OrgService.js";
|
||||
|
||||
export const handleGetOrgMembers = async (req: any, res: any) => {
|
||||
try {
|
||||
const { org, db } = req;
|
||||
const orgId = org.id;
|
||||
|
||||
const memberships = await OrgService.getMembers({ db, orgId });
|
||||
const invites = await OrgService.getInvites({ db, orgId });
|
||||
|
||||
res.status(200).json({
|
||||
memberships,
|
||||
invites,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error getting org members:", error);
|
||||
handleFrontendReqError({
|
||||
req,
|
||||
error,
|
||||
res,
|
||||
action: "get org members",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const handleRemoveMember = async (req: any, res: any) => {
|
||||
try {
|
||||
const { org, db } = req;
|
||||
const { memberId, userId } = req.body;
|
||||
const orgId = org.id;
|
||||
|
||||
// First check if the member exists by memberId
|
||||
let existingMember = await db.query.member.findFirst({
|
||||
where: and(eq(member.id, memberId), eq(member.organizationId, orgId)),
|
||||
});
|
||||
|
||||
if (!existingMember) {
|
||||
// Try to find by userId as fallback
|
||||
const memberByUserId = await db.query.member.findFirst({
|
||||
where: and(eq(member.userId, userId), eq(member.organizationId, orgId)),
|
||||
});
|
||||
|
||||
if (!memberByUserId) {
|
||||
console.warn(
|
||||
`Member not found by userId either: userId=${userId}, orgId=${orgId}`,
|
||||
);
|
||||
return res.status(404).json({
|
||||
message: "Member not found in this organization",
|
||||
code: "MEMBER_NOT_FOUND",
|
||||
});
|
||||
}
|
||||
|
||||
// Use the member found by userId
|
||||
existingMember = memberByUserId;
|
||||
}
|
||||
|
||||
// Remove member from database using the found member
|
||||
await db
|
||||
.delete(member)
|
||||
.where(
|
||||
and(eq(member.id, existingMember.id), eq(member.organizationId, orgId)),
|
||||
);
|
||||
|
||||
// Revoke all sessions for this user in this organization
|
||||
try {
|
||||
await db
|
||||
.delete(authSession)
|
||||
.where(
|
||||
and(
|
||||
eq(authSession.userId, existingMember.userId),
|
||||
eq(authSession.activeOrganizationId, orgId),
|
||||
),
|
||||
);
|
||||
} catch (error) {
|
||||
// Log but don't fail the request if session revocation fails
|
||||
req.logger?.warn(
|
||||
`Failed to revoke sessions for user ${existingMember.userId} in org ${orgId}:`,
|
||||
error,
|
||||
);
|
||||
console.warn("Session revocation failed:", error);
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
message: "Member removed successfully",
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error in handleRemoveMember:", error);
|
||||
handleFrontendReqError({
|
||||
req,
|
||||
error,
|
||||
res,
|
||||
action: "remove member",
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1,32 +1,25 @@
|
||||
import { ErrCode } from "@autumn/shared";
|
||||
import { logger } from "@/external/logtail/logtailUtils.js";
|
||||
import { getUploadUrl } from "@/external/supabase/storageUtils.js";
|
||||
import RecaseError, { handleFrontendReqError } from "@/utils/errorUtils.js";
|
||||
import RecaseError from "@/utils/errorUtils.js";
|
||||
import { createRoute } from "../../../honoMiddlewares/routeHandler";
|
||||
|
||||
export const handleGetUploadUrl = async (req: any, res: any) => {
|
||||
try {
|
||||
const { org } = req;
|
||||
export const handleGetUploadUrl = createRoute({
|
||||
handler: async (c) => {
|
||||
const ctx = c.get("ctx");
|
||||
const { org } = ctx;
|
||||
|
||||
const path = `logo/${org.id}`;
|
||||
|
||||
if (!process.env.SUPABASE_URL || !process.env.SUPABASE_SERVICE_KEY) {
|
||||
logger.warn("Supabase storage not set up");
|
||||
res.status(400).json({
|
||||
throw new RecaseError({
|
||||
message: "Supabase storage not set up",
|
||||
code: ErrCode.SupabaseNotFound,
|
||||
statusCode: 400,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await getUploadUrl({ path });
|
||||
|
||||
res.status(200).json(data);
|
||||
} catch (error) {
|
||||
handleFrontendReqError({
|
||||
req,
|
||||
error,
|
||||
res,
|
||||
action: "get upload url",
|
||||
return c.json(data);
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { invitation, user as userTable } from "@autumn/shared";
|
||||
import { and, eq, gt } from "drizzle-orm";
|
||||
import { createRoute } from "../../../../honoMiddlewares/routeHandler";
|
||||
|
||||
export const handleGetInvites = createRoute({
|
||||
handler: async (c) => {
|
||||
const ctx = c.get("ctx");
|
||||
const { userId, db } = ctx;
|
||||
|
||||
const user = await db.query.user.findFirst({
|
||||
where: eq(userTable.id, userId ?? ""),
|
||||
});
|
||||
|
||||
const invites = await db.query.invitation.findMany({
|
||||
where: and(
|
||||
eq(invitation.status, "pending"),
|
||||
eq(invitation.email, user?.email ?? ""),
|
||||
gt(invitation.expiresAt, new Date()),
|
||||
),
|
||||
with: {
|
||||
inviter: true,
|
||||
organization: true,
|
||||
},
|
||||
});
|
||||
|
||||
return c.json({ invites });
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
import { createRoute } from "../../../../honoMiddlewares/routeHandler.js";
|
||||
import { OrgService } from "../../OrgService.js";
|
||||
|
||||
export const handleGetOrgMembers = createRoute({
|
||||
handler: async (c) => {
|
||||
const ctx = c.get("ctx");
|
||||
const { db, org } = ctx;
|
||||
|
||||
const memberships = await OrgService.getMembers({ db, orgId: org.id });
|
||||
const invites = await OrgService.getInvites({ db, orgId: org.id });
|
||||
|
||||
return c.json({
|
||||
memberships,
|
||||
invites,
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,70 @@
|
||||
import { session as authSession, member, RecaseError } from "@autumn/shared";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import { z } from "zod/v4";
|
||||
import { createRoute } from "../../../../honoMiddlewares/routeHandler";
|
||||
|
||||
export const handleRemoveMember = createRoute({
|
||||
body: z.object({
|
||||
memberId: z.string(),
|
||||
userId: z.string(),
|
||||
}),
|
||||
handler: async (c) => {
|
||||
const ctx = c.get("ctx");
|
||||
const { memberId, userId } = c.req.valid("json");
|
||||
const { org, db } = ctx;
|
||||
|
||||
// First check if the member exists by memberId
|
||||
let existingMember = await db.query.member.findFirst({
|
||||
where: and(eq(member.id, memberId), eq(member.organizationId, org.id)),
|
||||
});
|
||||
|
||||
if (!existingMember) {
|
||||
// Try to find by userId as fallback
|
||||
const memberByUserId = await db.query.member.findFirst({
|
||||
where: and(
|
||||
eq(member.userId, userId),
|
||||
eq(member.organizationId, org.id),
|
||||
),
|
||||
});
|
||||
|
||||
if (!memberByUserId) {
|
||||
throw new RecaseError({
|
||||
message: "Member not found in this organization",
|
||||
code: "MEMBER_NOT_FOUND",
|
||||
statusCode: 404,
|
||||
});
|
||||
}
|
||||
|
||||
// Use the member found by userId
|
||||
existingMember = memberByUserId;
|
||||
}
|
||||
|
||||
// Remove member from database using the found member
|
||||
await db
|
||||
.delete(member)
|
||||
.where(
|
||||
and(
|
||||
eq(member.id, existingMember.id),
|
||||
eq(member.organizationId, org.id),
|
||||
),
|
||||
);
|
||||
|
||||
// Revoke all sessions for this user in this organization
|
||||
try {
|
||||
await db
|
||||
.delete(authSession)
|
||||
.where(
|
||||
and(
|
||||
eq(authSession.userId, existingMember.userId),
|
||||
eq(authSession.activeOrganizationId, org.id),
|
||||
),
|
||||
);
|
||||
} catch (error) {
|
||||
console.warn(`Session revocation failed: ${error}`);
|
||||
}
|
||||
|
||||
return c.json({
|
||||
message: "Member removed successfully",
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -1,13 +1,7 @@
|
||||
import express, { type Router } from "express";
|
||||
import { Hono } from "hono";
|
||||
import type { HonoEnv } from "@/honoUtils/HonoEnv.js";
|
||||
import { handleDeleteOrg } from "./handlers/handleDeleteOrg.js";
|
||||
import { handleGetInvites } from "./handlers/handleGetInvites.js";
|
||||
import { handleGetOrg } from "./handlers/handleGetOrg.js";
|
||||
import {
|
||||
handleGetOrgMembers,
|
||||
handleRemoveMember,
|
||||
} from "./handlers/handleGetOrgMembers.js";
|
||||
import { handleDeleteOrg } from "./handlers/crudHandlers/handleDeleteOrg.js";
|
||||
import { handleGetOrg } from "./handlers/crudHandlers/handleGetOrg.js";
|
||||
import { handleGetUploadUrl } from "./handlers/handleGetUploadUrl.js";
|
||||
import { handleResetDefaultAccount } from "./handlers/handleResetDefaultAccount.js";
|
||||
import { handleUpdateOrg } from "./handlers/handleUpdateOrg.js";
|
||||
@@ -15,30 +9,25 @@ import {
|
||||
handleGetVercelSink,
|
||||
handleUpsertVercelConfig,
|
||||
} from "./handlers/handleVercelConfig.js";
|
||||
import { handleGetInvites } from "./handlers/memberHandlers/handleGetInvites.js";
|
||||
import { handleGetOrgMembers } from "./handlers/memberHandlers/handleGetOrgMembers.js";
|
||||
import { handleRemoveMember } from "./handlers/memberHandlers/handleRemoveMember.js";
|
||||
import { handleConnectStripe } from "./handlers/stripeHandlers/handleConnectStripe.js";
|
||||
import { handleDeleteStripe } from "./handlers/stripeHandlers/handleDeleteStripe.js";
|
||||
import { handleGetOAuthUrl } from "./handlers/stripeHandlers/handleGetOAuthUrl.js";
|
||||
import { handleGetStripeAccount } from "./handlers/stripeHandlers/handleGetStripeAccount.js";
|
||||
|
||||
export const orgRouter: Router = express.Router();
|
||||
orgRouter.get("/members", handleGetOrgMembers);
|
||||
orgRouter.post("/remove-member", handleRemoveMember);
|
||||
orgRouter.get("/upload_url", handleGetUploadUrl);
|
||||
orgRouter.get("/invites", handleGetInvites as any);
|
||||
orgRouter.delete("", handleDeleteOrg as any);
|
||||
export const internalOrgRouter = new Hono<HonoEnv>();
|
||||
|
||||
orgRouter.delete("/delete-user", async (req: any, res) => {
|
||||
res.status(200).json({
|
||||
message: "User deleted",
|
||||
});
|
||||
});
|
||||
|
||||
orgRouter.get("", handleGetOrg);
|
||||
|
||||
// orgRouter.post("/stripe", handleConnectStripe);
|
||||
internalOrgRouter.get("", ...handleGetOrg);
|
||||
internalOrgRouter.delete("", ...handleDeleteOrg);
|
||||
internalOrgRouter.get("/members", ...handleGetOrgMembers);
|
||||
internalOrgRouter.post("/remove-member", ...handleRemoveMember);
|
||||
internalOrgRouter.get("/upload_url", ...handleGetUploadUrl);
|
||||
internalOrgRouter.get("/invites", ...handleGetInvites);
|
||||
|
||||
export const honoOrgRouter = new Hono<HonoEnv>();
|
||||
|
||||
honoOrgRouter.get("", ...handleGetOrg);
|
||||
honoOrgRouter.patch("", ...handleUpdateOrg);
|
||||
honoOrgRouter.get("/stripe", ...handleGetStripeAccount);
|
||||
honoOrgRouter.delete("/stripe", ...handleDeleteStripe);
|
||||
|
||||
30
server/src/routers/internalRouter.ts
Normal file
30
server/src/routers/internalRouter.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { Hono } from "hono";
|
||||
import { adminAuthMiddleware } from "../honoMiddlewares/adminAuthMiddleware";
|
||||
import { analyticsMiddleware } from "../honoMiddlewares/analyticsMiddleware";
|
||||
import { apiVersionMiddleware } from "../honoMiddlewares/apiVersionMiddleware";
|
||||
import { betterAuthMiddleware } from "../honoMiddlewares/betterAuthMiddleware";
|
||||
import { orgConfigMiddleware } from "../honoMiddlewares/orgConfigMiddleware";
|
||||
import { queryMiddleware } from "../honoMiddlewares/queryMiddleware";
|
||||
import { refreshCacheMiddleware } from "../honoMiddlewares/refreshCacheMiddleware";
|
||||
import type { HonoEnv } from "../honoUtils/HonoEnv";
|
||||
import { honoAdminRouter } from "../internal/admin/adminRouter";
|
||||
import { internalCusRouter } from "../internal/customers/internalCusRouter";
|
||||
import { internalOrgRouter } from "../internal/orgs/orgRouter";
|
||||
import { internalProductRouter } from "../internal/products/internalProductRouter";
|
||||
|
||||
export const internalRouter = new Hono<HonoEnv>();
|
||||
|
||||
// Internal/dashboard routes - use betterAuthMiddleware for session auth
|
||||
internalRouter.use("*", betterAuthMiddleware);
|
||||
internalRouter.use("*", orgConfigMiddleware);
|
||||
internalRouter.use("*", apiVersionMiddleware);
|
||||
internalRouter.use("*", analyticsMiddleware);
|
||||
internalRouter.use("*", refreshCacheMiddleware);
|
||||
internalRouter.use("*", queryMiddleware());
|
||||
|
||||
internalRouter.use("/admin/*", adminAuthMiddleware);
|
||||
internalRouter.route("admin", honoAdminRouter);
|
||||
|
||||
internalRouter.route("organization", internalOrgRouter);
|
||||
internalRouter.route("/products", internalProductRouter);
|
||||
internalRouter.route("/customers", internalCusRouter);
|
||||
Reference in New Issue
Block a user