feat(cloud): improved workspace onboarding (#462)
* feat: setup onboarding and select plan page * feat: add workspace details step * refactor: optimize board rendering in workspace details * feat: tweak padding in boards mockup * fix: validate/convert slug input * refactor: tweak onboarding steps UX * feat: add team to workspace plans * feat: allow setting description when creating a workspace * feat: add logging to next api routes * feat: create worspace on checkout.session.completed * chore: update types * refactor: improve webhook logs * feat: create workspace on successful activation * feat: set slug on workspace name change * feat: add returnUrl params
This commit is contained in:
@@ -202,6 +202,7 @@ export const workspaceRouter = createTRPCRouter({
|
||||
.input(
|
||||
z.object({
|
||||
name: z.string().min(1).max(64),
|
||||
description: z.string().max(280).optional(),
|
||||
slug: z
|
||||
.string()
|
||||
.min(3)
|
||||
@@ -260,6 +261,7 @@ export const workspaceRouter = createTRPCRouter({
|
||||
slug: workspaceSlug,
|
||||
createdBy: userId,
|
||||
createdByEmail: userEmail,
|
||||
...(input.description && { description: input.description }),
|
||||
});
|
||||
|
||||
if (!result.publicId)
|
||||
|
||||
64
packages/api/src/utils/apiLogging.ts
Normal file
64
packages/api/src/utils/apiLogging.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { randomUUID } from "crypto";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { createLogger } from "@kan/logger";
|
||||
|
||||
import { createNextApiContext } from "../trpc";
|
||||
|
||||
const log = createLogger("api");
|
||||
|
||||
const isCloud = process.env.NEXT_PUBLIC_KAN_ENV === "cloud";
|
||||
|
||||
export function withApiLogging(
|
||||
handler: (
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse,
|
||||
) => Promise<unknown> | unknown,
|
||||
) {
|
||||
return async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const start = Date.now();
|
||||
const requestId = randomUUID();
|
||||
const route = req.url?.split("?")[0] ?? "unknown";
|
||||
const input = {
|
||||
...(req.query && Object.keys(req.query).length > 0 && { query: req.query }),
|
||||
...(req.body && typeof req.body === "object" && Object.keys(req.body).length > 0 && { body: req.body }),
|
||||
};
|
||||
|
||||
let statusCode = 200;
|
||||
const originalStatus = res.status.bind(res);
|
||||
res.status = (code: number) => {
|
||||
statusCode = code;
|
||||
return originalStatus(code);
|
||||
};
|
||||
|
||||
let userId: string | undefined;
|
||||
let email: string | undefined;
|
||||
try {
|
||||
const ctx = await createNextApiContext(req);
|
||||
userId = ctx.user?.id;
|
||||
email = ctx.user?.email ?? undefined;
|
||||
} catch {
|
||||
// unauthenticated or auth unavailable
|
||||
}
|
||||
|
||||
await handler(req, res);
|
||||
|
||||
const duration = Date.now() - start;
|
||||
const meta = {
|
||||
requestId,
|
||||
procedure: route,
|
||||
transport: "rest",
|
||||
duration,
|
||||
userId,
|
||||
...(isCloud && email && { email }),
|
||||
...(Object.keys(input).length > 0 && { input }),
|
||||
status: statusCode,
|
||||
};
|
||||
|
||||
if (statusCode < 400) {
|
||||
log.info(meta, "API OK");
|
||||
} else {
|
||||
log.error(meta, "API error");
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user