Files
zeekayandHanzo Dev 6b68c9299e kv: one name for the store, and it is Hanzo KV
The client was already @hanzo/kv; only the vocabulary was still Redis. Every
module, symbol and comment now says what it connects to:

  lib/redis.ts                    -> lib/kv.ts                (exports kv, lockKv)
  lib/redis-job-store.ts          -> lib/kv-job-store.ts      (KvJobStore)
  lib/redis-download-job-store.ts -> lib/kv-download-job-store.ts
  lib/files/tus-redis-locker.ts   -> lib/files/tus-kv-locker.ts (KvLocker, KvLock)
  lib/api/domains/redis.ts        -> lib/api/domains/kv.ts    (getKey)
  lib/redis/                      -> lib/kv-queue/

KV_URL is now the only endpoint variable — the REDIS_URL fallback and the
redis://localhost default are gone, and the default scheme is kv://, which
@hanzo/kv parses natively (utils/index.js). .env.example gained the KV_URL it
never documented, and lost UPSTASH_REDIS_REST_LOCKER_URL/TOKEN: nothing has read
those since the client changed — the locker opens a second connection to the
same KV, so there is no second endpoint to configure.

The second connection is `lockKv`, not `locker`: both tus routes already have a
local `const locker = new KvLocker(...)`, so naming the client `locker` made the
lock take itself as its own client.

Verified against the repo's own gate (npx tsc --noEmit): 100 errors before and
after, 0 introduced, compared set-wise by file:line:code. Behaviour is untouched
— this is naming, one connection string, and dead config.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-09 10:09:34 -07:00

270 lines
7.2 KiB
TypeScript

import { NextApiRequest, NextApiResponse } from "next";
import { authOptions } from "@/pages/api//auth/[...nextauth]";
import { getServerSession } from "next-auth/next";
import { z } from "zod";
import {
deleteDomainRedirectUrl,
setDomainRedirectUrl,
} from "@/lib/api/domains/kv";
import { validateRedirectUrl } from "@/lib/api/domains/validate-redirect-url";
import { getApexDomain, removeDomainFromVercel } from "@/lib/domains";
import { errorhandler } from "@/lib/errorHandler";
import prisma from "@/lib/prisma";
import { CustomUser } from "@/lib/types";
import { log } from "@/lib/utils";
const updateDomainSchema = z.object({
redirectUrl: z.string().nullable().optional(),
});
export default async function handle(
req: NextApiRequest,
res: NextApiResponse,
) {
if (req.method === "DELETE") {
// DELETE /api/teams/:teamId/domains/:domain
const session = await getServerSession(req, res, authOptions);
if (!session) {
return res.status(401).end("Unauthorized");
}
// Assuming the domain slug is sent in the request body.
const { teamId, domain } = req.query as { teamId: string; domain: string };
const userId = (session.user as CustomUser).id;
if (!domain) {
return res.status(400).json("Domain is required for deletion");
}
try {
const hasTeamAccess = await prisma.userTeam.findUnique({
where: {
userId_teamId: {
userId,
teamId,
},
},
});
if (!hasTeamAccess) {
return res.status(401).end("Unauthorized");
}
const domainToBeDeleted = await prisma.domain.findUnique({
where: {
slug: domain,
teamId,
},
select: {
id: true,
},
});
if (!domainToBeDeleted) {
return res.status(404).json("Domain not found");
}
// calculate the domainCount
const apexDomain = getApexDomain(`https://${domain}`);
const domainCount = await prisma.domain.count({
where: {
OR: [
{
slug: apexDomain,
},
{
slug: {
endsWith: `.${apexDomain}`,
},
},
],
},
});
await Promise.allSettled([
removeDomainFromVercel(domain, domainCount),
prisma.domain.delete({
where: {
id: domainToBeDeleted.id,
},
}),
deleteDomainRedirectUrl(domain),
]);
return res.status(204).end();
} catch (error) {
log({
message: `Failed to delete domain: _${domain}_. \n\n ${error} \n\n*Metadata*: \`{teamId: ${teamId}, userId: ${userId}}\``,
type: "error",
mention: true,
});
errorhandler(error, res);
}
} else if (req.method === "PATCH") {
// PATCH /api/teams/:teamId/domains/:domain
const session = await getServerSession(req, res, authOptions);
if (!session) {
return res.status(401).end("Unauthorized");
}
// Assuming the domain slug is sent in the request body.
const { teamId, domain } = req.query as { teamId: string; domain: string };
const userId = (session.user as CustomUser).id;
if (!domain) {
return res.status(400).json("Domain is required for deletion");
}
try {
const hasTeamAccess = await prisma.userTeam.findUnique({
where: {
userId_teamId: {
userId,
teamId,
},
},
});
if (!hasTeamAccess) {
return res.status(401).end("Unauthorized");
}
const domainToBeUpdated = await prisma.domain.findUnique({
where: {
slug: domain,
teamId,
},
select: {
id: true,
},
});
if (!domainToBeUpdated) {
return res.status(404).json("Domain not found");
}
const updateDefaultPromise = prisma.domain.update({
where: {
id: domainToBeUpdated.id,
teamId: teamId,
},
data: {
isDefault: true,
},
});
const updateNonDefaultPromise = prisma.domain.updateMany({
where: {
teamId,
slug: {
not: domain,
},
},
data: {
isDefault: false,
},
});
await Promise.all([updateDefaultPromise, updateNonDefaultPromise]);
return res.status(200).json({ message: "Domain set to default" }); // 204 No Content response for successful deletes
} catch (error) {
log({
message: `Failed to set domain: _${domain}_. \n\n ${error} \n\n*Metadata*: \`{teamId: ${teamId}, userId: ${userId}}\``,
type: "error",
mention: true,
});
errorhandler(error, res);
}
} else if (req.method === "PUT") {
// PUT /api/teams/:teamId/domains/:domain - update domain settings (e.g. redirectUrl)
const session = await getServerSession(req, res, authOptions);
if (!session) {
return res.status(401).end("Unauthorized");
}
const { teamId, domain } = req.query as { teamId: string; domain: string };
const userId = (session.user as CustomUser).id;
if (!domain) {
return res.status(400).json("Domain is required");
}
try {
const hasTeamAccess = await prisma.userTeam.findUnique({
where: {
userId_teamId: {
userId,
teamId,
},
},
});
if (!hasTeamAccess) {
return res.status(401).end("Unauthorized");
}
const domainToBeUpdated = await prisma.domain.findUnique({
where: {
slug: domain,
teamId,
},
select: {
id: true,
},
});
if (!domainToBeUpdated) {
return res.status(404).json("Domain not found");
}
const parsed = updateDomainSchema.safeParse(req.body);
if (!parsed.success) {
return res
.status(422)
.json({ message: parsed.error.issues[0]?.message ?? "Invalid body" });
}
const hasRedirectUrl = "redirectUrl" in parsed.data;
const redirectUrl = parsed.data.redirectUrl;
let normalizedUrl: string | null | undefined;
if (hasRedirectUrl) {
if (redirectUrl) {
const result = await validateRedirectUrl(redirectUrl, teamId);
if (!result.valid) {
return res.status(422).json({ message: result.message });
}
normalizedUrl = result.url || null;
} else {
normalizedUrl = null;
}
}
const updatedDomain = await prisma.domain.update({
where: {
id: domainToBeUpdated.id,
teamId,
},
data: {
...(normalizedUrl !== undefined && { redirectUrl: normalizedUrl }),
},
});
if (normalizedUrl !== undefined) {
await setDomainRedirectUrl(domain, normalizedUrl);
}
return res.status(200).json(updatedDomain);
} catch (error) {
log({
message: `Failed to update domain: _${domain}_. \n\n ${error} \n\n*Metadata*: \`{teamId: ${teamId}, userId: ${userId}}\``,
type: "error",
mention: true,
});
errorhandler(error, res);
}
} else {
res.setHeader("Allow", ["DELETE", "PATCH", "PUT"]);
return res.status(405).end(`Method ${req.method} Not Allowed`);
}
}