Files
Marc SeitzandHanzo Dev 02daf86538 fix: add CJK-safe slugify with nanoid fallback
slugify strips all CJK characters, producing empty strings for
filenames and folder names that contain only CJK text. Replace all
usages with safeSlugify which falls back to a 12-char lowercase
alphanumeric nanoid when slugify returns an empty result.

Co-authored-by: Hanzo Dev <[email protected]>
2026-02-25 14:42:53 +11:00

213 lines
5.4 KiB
TypeScript

import { NextApiRequest, NextApiResponse } from "next";
import { authOptions } from "@/pages/api/auth/[...nextauth]";
import { safeSlugify } from "@/lib/utils";
import { getServerSession } from "next-auth/next";
import prisma from "@/lib/prisma";
import { CustomUser } from "@/lib/types";
export default async function handle(
req: NextApiRequest,
res: NextApiResponse,
) {
if (req.method === "GET") {
// GET /api/teams/:teamId/folders
const session = await getServerSession(req, res, authOptions);
if (!session) {
return res.status(401).end("Unauthorized");
}
const userId = (session.user as CustomUser).id;
const { teamId, root } = req.query as { teamId: string; root?: string };
try {
// Check if the user is part of the team
const teamAccess = await prisma.userTeam.findUnique({
where: {
userId_teamId: {
userId: userId,
teamId: teamId,
},
},
});
if (!teamAccess) {
return res.status(401).end("Unauthorized");
}
/** if root is present then only get root folders */
if (root === "true") {
const folders = await prisma.folder.findMany({
where: {
teamId: teamId,
parentId: null,
hiddenInAllDocuments: false, // Exclude hidden folders from All Documents view
},
orderBy: {
name: "asc",
},
include: {
_count: {
select: {
documents: {
where: { hiddenInAllDocuments: false },
},
childFolders: {
where: { hiddenInAllDocuments: false },
},
},
},
},
});
return res.status(200).json(folders);
}
const folders = await prisma.folder.findMany({
where: {
teamId: teamId,
},
orderBy: {
name: "asc",
},
include: {
documents: {
select: {
id: true,
name: true,
folderId: true,
},
},
childFolders: {
include: {
documents: {
select: {
id: true,
name: true,
folderId: true,
},
},
},
},
},
});
return res.status(200).json(folders);
} catch (error) {
console.error("Request error", error);
return res.status(500).json({ error: "Error fetching folders" });
}
} else if (req.method === "POST") {
// POST /api/teams/:teamId/folders
const session = await getServerSession(req, res, authOptions);
if (!session) {
res.status(401).end("Unauthorized");
return;
}
const userId = (session.user as CustomUser).id;
const { teamId } = req.query as { teamId: string };
const { name, path, icon, color } = req.body as {
name: string;
path: string;
icon?: string;
color?: string;
};
const parentFolderPath = path ? "/" + path : "/";
try {
// Check if the user is part of the team
const teamAccess = await prisma.userTeam.findUnique({
where: {
userId_teamId: {
userId: userId,
teamId: teamId,
},
},
});
if (!teamAccess) {
return res.status(401).end("Unauthorized");
}
const parentFolder = await prisma.folder.findUnique({
where: {
teamId_path: {
teamId: teamId,
path: parentFolderPath,
},
},
select: {
id: true,
name: true,
path: true,
},
});
let folderName = name;
let counter = 1;
const MAX_RETRIES = 50;
let childFolderPath = path
? "/" + path + "/" + safeSlugify(folderName)
: "/" + safeSlugify(folderName);
while (counter <= MAX_RETRIES) {
const existingFolder = await prisma.folder.findUnique({
where: {
teamId_path: {
teamId: teamId,
path: childFolderPath,
},
},
});
if (!existingFolder) break;
folderName = `${name} (${counter})`;
childFolderPath = path
? "/" + path + "/" + safeSlugify(folderName)
: "/" + safeSlugify(folderName);
counter++;
}
if (counter > MAX_RETRIES) {
return res.status(400).json({
error: "Failed to create folder",
message: "Too many folders with similar names",
});
}
const folder = await prisma.folder.create({
data: {
name: folderName,
path: childFolderPath,
parentId: parentFolder?.id ?? null,
teamId: teamId,
icon: icon ?? null,
color: color ?? null,
},
});
const folderWithDocs = {
...folder,
documents: [],
childFolders: [],
parentFolderPath: parentFolderPath,
};
res.status(201).json(folderWithDocs);
} catch (error) {
console.error("Request error", error);
res.status(500).json({ error: "Error creating folder" });
}
} else {
// We only allow POST requests
res.setHeader("Allow", ["GET", "POST"]);
return res.status(405).end(`Method ${req.method} Not Allowed`);
}
}