Compare commits
3 Commits
cc373f53fe
...
c6e60cd34d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c6e60cd34d | ||
|
|
c7f7492b9e | ||
|
|
4d310346c1 |
@@ -124,7 +124,6 @@ model Board {
|
|||||||
description String?
|
description String?
|
||||||
sortOrder Int @default(0)
|
sortOrder Int @default(0)
|
||||||
status BoardStatus @default(active)
|
status BoardStatus @default(active)
|
||||||
requiresApproval Boolean @default(false) // 게시물 승인 필요 여부
|
|
||||||
allowAnonymousPost Boolean @default(false) // 익명 글 허용
|
allowAnonymousPost Boolean @default(false) // 익명 글 허용
|
||||||
allowSecretComment Boolean @default(false) // 비밀댓글 허용
|
allowSecretComment Boolean @default(false) // 비밀댓글 허용
|
||||||
isAdultOnly Boolean @default(false) // 성인 인증 필요 여부
|
isAdultOnly Boolean @default(false) // 성인 인증 필요 여부
|
||||||
|
|||||||
@@ -200,6 +200,10 @@ async function upsertBoards(admin, categoryMap) {
|
|||||||
];
|
];
|
||||||
|
|
||||||
const created = [];
|
const created = [];
|
||||||
|
// 특수 랭킹 뷰 타입 ID 조회 (사전에 upsertViewTypes로 생성됨)
|
||||||
|
const mainSpecial = await prisma.boardViewType.findUnique({ where: { key: "main_special_rank" } });
|
||||||
|
const listSpecial = await prisma.boardViewType.findUnique({ where: { key: "list_special_rank" } });
|
||||||
|
|
||||||
for (const b of boards) {
|
for (const b of boards) {
|
||||||
// 카테고리 매핑 규칙 (트리 기준 상위 카테고리)
|
// 카테고리 매핑 규칙 (트리 기준 상위 카테고리)
|
||||||
const mapBySlug = {
|
const mapBySlug = {
|
||||||
@@ -227,20 +231,22 @@ async function upsertBoards(admin, categoryMap) {
|
|||||||
update: {
|
update: {
|
||||||
description: b.description,
|
description: b.description,
|
||||||
sortOrder: b.sortOrder,
|
sortOrder: b.sortOrder,
|
||||||
requiresApproval: !!b.requiresApproval,
|
|
||||||
allowAnonymousPost: !!b.allowAnonymousPost,
|
allowAnonymousPost: !!b.allowAnonymousPost,
|
||||||
readLevel: b.readLevel || undefined,
|
readLevel: b.readLevel || undefined,
|
||||||
categoryId: category ? category.id : undefined,
|
categoryId: category ? category.id : undefined,
|
||||||
|
...(b.slug === "ranking" && mainSpecial ? { mainPageViewTypeId: mainSpecial.id } : {}),
|
||||||
|
...(b.slug === "ranking" && listSpecial ? { listViewTypeId: listSpecial.id } : {}),
|
||||||
},
|
},
|
||||||
create: {
|
create: {
|
||||||
name: b.name,
|
name: b.name,
|
||||||
slug: b.slug,
|
slug: b.slug,
|
||||||
description: b.description,
|
description: b.description,
|
||||||
sortOrder: b.sortOrder,
|
sortOrder: b.sortOrder,
|
||||||
requiresApproval: !!b.requiresApproval,
|
|
||||||
allowAnonymousPost: !!b.allowAnonymousPost,
|
allowAnonymousPost: !!b.allowAnonymousPost,
|
||||||
readLevel: b.readLevel || undefined,
|
readLevel: b.readLevel || undefined,
|
||||||
categoryId: category ? category.id : undefined,
|
categoryId: category ? category.id : undefined,
|
||||||
|
...(b.slug === "ranking" && mainSpecial ? { mainPageViewTypeId: mainSpecial.id } : {}),
|
||||||
|
...(b.slug === "ranking" && listSpecial ? { listViewTypeId: listSpecial.id } : {}),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
created.push(board);
|
created.push(board);
|
||||||
|
|||||||
BIN
public/uploads/1762025382776-83vifeqk7rk.webp
Normal file
BIN
public/uploads/1762025382776-83vifeqk7rk.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 279 KiB |
@@ -368,7 +368,6 @@ export default function AdminBoardsPage() {
|
|||||||
<th className="px-3 py-2">쓰기</th>
|
<th className="px-3 py-2">쓰기</th>
|
||||||
<th className="px-3 py-2">익명</th>
|
<th className="px-3 py-2">익명</th>
|
||||||
<th className="px-3 py-2">비밀댓</th>
|
<th className="px-3 py-2">비밀댓</th>
|
||||||
<th className="px-3 py-2">승인</th>
|
|
||||||
<th className="px-3 py-2">성인</th>
|
<th className="px-3 py-2">성인</th>
|
||||||
<th className="px-3 py-2">대분류 이동</th>
|
<th className="px-3 py-2">대분류 이동</th>
|
||||||
<th className="px-3 py-2">활성</th>
|
<th className="px-3 py-2">활성</th>
|
||||||
@@ -501,7 +500,6 @@ function BoardRowCells({ b, onDirty, onDelete, allowMove, categories, onMove, ma
|
|||||||
</td>
|
</td>
|
||||||
<td className="px-3 py-2 text-center"><input type="checkbox" checked={edit.allowAnonymousPost} onChange={(e) => { const v = { ...edit, allowAnonymousPost: e.target.checked }; setEdit(v); onDirty(b.id, v); }} /></td>
|
<td className="px-3 py-2 text-center"><input type="checkbox" checked={edit.allowAnonymousPost} onChange={(e) => { const v = { ...edit, allowAnonymousPost: e.target.checked }; setEdit(v); onDirty(b.id, v); }} /></td>
|
||||||
<td className="px-3 py-2 text-center"><input type="checkbox" checked={edit.allowSecretComment} onChange={(e) => { const v = { ...edit, allowSecretComment: e.target.checked }; setEdit(v); onDirty(b.id, v); }} /></td>
|
<td className="px-3 py-2 text-center"><input type="checkbox" checked={edit.allowSecretComment} onChange={(e) => { const v = { ...edit, allowSecretComment: e.target.checked }; setEdit(v); onDirty(b.id, v); }} /></td>
|
||||||
<td className="px-3 py-2 text-center"><input type="checkbox" checked={edit.requiresApproval} onChange={(e) => { const v = { ...edit, requiresApproval: e.target.checked }; setEdit(v); onDirty(b.id, v); }} /></td>
|
|
||||||
<td className="px-3 py-2 text-center"><input type="checkbox" checked={!!edit.isAdultOnly} onChange={(e) => { const v = { ...edit, isAdultOnly: e.target.checked }; setEdit(v); onDirty(b.id, v); }} /></td>
|
<td className="px-3 py-2 text-center"><input type="checkbox" checked={!!edit.isAdultOnly} onChange={(e) => { const v = { ...edit, isAdultOnly: e.target.checked }; setEdit(v); onDirty(b.id, v); }} /></td>
|
||||||
{allowMove && categories && onMove ? (
|
{allowMove && categories && onMove ? (
|
||||||
<td className="px-3 py-2 text-center">
|
<td className="px-3 py-2 text-center">
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ export async function PATCH(req: Request, context: { params: Promise<{ id: strin
|
|||||||
const { id } = await context.params;
|
const { id } = await context.params;
|
||||||
const body = await req.json().catch(() => ({}));
|
const body = await req.json().catch(() => ({}));
|
||||||
const data: any = {};
|
const data: any = {};
|
||||||
for (const k of ["name", "slug", "description", "sortOrder", "readLevel", "writeLevel", "allowAnonymousPost", "allowSecretComment", "requiresApproval", "status", "isAdultOnly", "categoryId", "mainPageViewTypeId", "listViewTypeId"]) {
|
for (const k of ["name", "slug", "description", "sortOrder", "readLevel", "writeLevel", "allowAnonymousPost", "allowSecretComment", "status", "isAdultOnly", "categoryId", "mainPageViewTypeId", "listViewTypeId"]) {
|
||||||
if (k in body) data[k] = body[k];
|
if (k in body) data[k] = body[k];
|
||||||
}
|
}
|
||||||
if ("requiredTags" in body) {
|
if ("requiredTags" in body) {
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ export async function GET() {
|
|||||||
writeLevel: true,
|
writeLevel: true,
|
||||||
allowAnonymousPost: true,
|
allowAnonymousPost: true,
|
||||||
allowSecretComment: true,
|
allowSecretComment: true,
|
||||||
requiresApproval: true,
|
|
||||||
status: true,
|
status: true,
|
||||||
categoryId: true,
|
categoryId: true,
|
||||||
mainPageViewTypeId: true,
|
mainPageViewTypeId: true,
|
||||||
@@ -36,7 +35,6 @@ const createSchema = z.object({
|
|||||||
writeLevel: z.string().optional(),
|
writeLevel: z.string().optional(),
|
||||||
allowAnonymousPost: z.boolean().optional(),
|
allowAnonymousPost: z.boolean().optional(),
|
||||||
allowSecretComment: z.boolean().optional(),
|
allowSecretComment: z.boolean().optional(),
|
||||||
requiresApproval: z.boolean().optional(),
|
|
||||||
status: z.string().optional(),
|
status: z.string().optional(),
|
||||||
isAdultOnly: z.boolean().optional(),
|
isAdultOnly: z.boolean().optional(),
|
||||||
categoryId: z.string().nullable().optional(),
|
categoryId: z.string().nullable().optional(),
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ export async function GET(req: Request) {
|
|||||||
name: true,
|
name: true,
|
||||||
slug: true,
|
slug: true,
|
||||||
description: true,
|
description: true,
|
||||||
requiresApproval: true,
|
|
||||||
allowAnonymousPost: true,
|
allowAnonymousPost: true,
|
||||||
isAdultOnly: true,
|
isAdultOnly: true,
|
||||||
category: { select: { id: true, name: true, slug: true } },
|
category: { select: { id: true, name: true, slug: true } },
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export async function GET() {
|
|||||||
boards: {
|
boards: {
|
||||||
where: { status: "active" },
|
where: { status: "active" },
|
||||||
orderBy: [{ sortOrder: "asc" }, { createdAt: "asc" }],
|
orderBy: [{ sortOrder: "asc" }, { createdAt: "asc" }],
|
||||||
select: { id: true, name: true, slug: true, requiresApproval: true },
|
select: { id: true, name: true, slug: true },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ export async function POST(req: Request) {
|
|||||||
}
|
}
|
||||||
const { boardId, authorId, title, content, isAnonymous } = parsed.data;
|
const { boardId, authorId, title, content, isAnonymous } = parsed.data;
|
||||||
const board = await prisma.board.findUnique({ where: { id: boardId } });
|
const board = await prisma.board.findUnique({ where: { id: boardId } });
|
||||||
const requiresApproval = board?.requiresApproval ?? false;
|
|
||||||
// 사진형 보드 필수 이미지 검증: content 내 이미지 링크 최소 1개
|
// 사진형 보드 필수 이미지 검증: content 내 이미지 링크 최소 1개
|
||||||
const isImageOnly = (board?.requiredFields as any)?.imageOnly;
|
const isImageOnly = (board?.requiredFields as any)?.imageOnly;
|
||||||
const minImages = (board?.requiredFields as any)?.minImages ?? 0;
|
const minImages = (board?.requiredFields as any)?.minImages ?? 0;
|
||||||
@@ -35,7 +34,7 @@ export async function POST(req: Request) {
|
|||||||
title,
|
title,
|
||||||
content,
|
content,
|
||||||
isAnonymous: !!isAnonymous,
|
isAnonymous: !!isAnonymous,
|
||||||
status: requiresApproval ? "hidden" : "published",
|
status: "published",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return NextResponse.json({ post }, { status: 201 });
|
return NextResponse.json({ post }, { status: 201 });
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import prisma from "@/lib/prisma";
|
|||||||
export default async function BoardDetail({ params, searchParams }: { params: any; searchParams: any }) {
|
export default async function BoardDetail({ params, searchParams }: { params: any; searchParams: any }) {
|
||||||
const p = params?.then ? await params : params;
|
const p = params?.then ? await params : params;
|
||||||
const sp = searchParams?.then ? await searchParams : searchParams;
|
const sp = searchParams?.then ? await searchParams : searchParams;
|
||||||
const id = p.id as string;
|
const idOrSlug = p.id as string;
|
||||||
const sort = (sp?.sort as "recent" | "popular" | undefined) ?? "recent";
|
const sort = (sp?.sort as "recent" | "popular" | undefined) ?? "recent";
|
||||||
const period = (sp?.period as string | undefined) ?? "monthly";
|
const period = (sp?.period as string | undefined) ?? "monthly";
|
||||||
// 보드 slug 조회 (새 글 페이지 프리셋 전달)
|
// 보드 slug 조회 (새 글 페이지 프리셋 전달)
|
||||||
@@ -18,7 +18,8 @@ export default async function BoardDetail({ params, searchParams }: { params: an
|
|||||||
const base = process.env.NEXT_PUBLIC_BASE_URL || `${proto}://${host}`;
|
const base = process.env.NEXT_PUBLIC_BASE_URL || `${proto}://${host}`;
|
||||||
const res = await fetch(new URL("/api/boards", base).toString(), { cache: "no-store" });
|
const res = await fetch(new URL("/api/boards", base).toString(), { cache: "no-store" });
|
||||||
const { boards } = await res.json();
|
const { boards } = await res.json();
|
||||||
const board = (boards || []).find((b: any) => b.id === id);
|
const board = (boards || []).find((b: any) => b.slug === idOrSlug || b.id === idOrSlug);
|
||||||
|
const id = board?.id as string;
|
||||||
const siblingBoards = (boards || []).filter((b: any) => b.category?.id && b.category.id === board?.category?.id);
|
const siblingBoards = (boards || []).filter((b: any) => b.category?.id && b.category.id === board?.category?.id);
|
||||||
const categoryName = board?.category?.name ?? "";
|
const categoryName = board?.category?.name ?? "";
|
||||||
|
|
||||||
@@ -41,14 +42,14 @@ export default async function BoardDetail({ params, searchParams }: { params: an
|
|||||||
{/* 상단 배너 (서브카테고리 표시) */}
|
{/* 상단 배너 (서브카테고리 표시) */}
|
||||||
<section>
|
<section>
|
||||||
<HeroBanner
|
<HeroBanner
|
||||||
subItems={siblingBoards.map((b: any) => ({ id: b.id, name: b.name, href: `/boards/${b.id}` }))}
|
subItems={siblingBoards.map((b: any) => ({ id: b.id, name: b.name, href: `/boards/${b.slug}` }))}
|
||||||
activeSubId={id}
|
activeSubId={id}
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* 검색/필터 툴바 + 리스트 */}
|
{/* 검색/필터 툴바 + 리스트 */}
|
||||||
<section>
|
<section>
|
||||||
<BoardToolbar boardId={id} />
|
<BoardToolbar boardId={board?.slug} />
|
||||||
<div className="p-0">
|
<div className="p-0">
|
||||||
{isSpecialRanking ? (
|
{isSpecialRanking ? (
|
||||||
<div className="rounded-xl border border-neutral-200 overflow-hidden">
|
<div className="rounded-xl border border-neutral-200 overflow-hidden">
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export default async function BoardsPage() {
|
|||||||
<h1>게시판</h1>
|
<h1>게시판</h1>
|
||||||
<ul style={{ display: "flex", flexDirection: "column", gap: 6 }}>
|
<ul style={{ display: "flex", flexDirection: "column", gap: 6 }}>
|
||||||
{boards?.map((b: any) => (
|
{boards?.map((b: any) => (
|
||||||
<li key={b.id}><Link href={`/boards/${b.id}`}>{b.name}</Link></li>
|
<li key={b.id}><Link href={`/boards/${b.slug}`}>{b.name}</Link></li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export function AppHeader() {
|
|||||||
}, [pathname]);
|
}, [pathname]);
|
||||||
const activeCategorySlug = React.useMemo(() => {
|
const activeCategorySlug = React.useMemo(() => {
|
||||||
if (activeBoardId) {
|
if (activeBoardId) {
|
||||||
const found = categories.find((c) => c.boards.some((b) => b.id === activeBoardId));
|
const found = categories.find((c) => c.boards.some((b) => b.slug === activeBoardId));
|
||||||
return found?.slug ?? null;
|
return found?.slug ?? null;
|
||||||
}
|
}
|
||||||
if (pathname === "/boards") {
|
if (pathname === "/boards") {
|
||||||
@@ -337,7 +337,7 @@ export function AppHeader() {
|
|||||||
style={idx === categories.length - 1 ? { minWidth: 120 } : undefined}
|
style={idx === categories.length - 1 ? { minWidth: 120 } : undefined}
|
||||||
>
|
>
|
||||||
<Link
|
<Link
|
||||||
href={cat.boards?.[0]?.id ? `/boards/${cat.boards[0].id}` : `/boards?category=${cat.slug}`}
|
href={cat.boards?.[0]?.slug ? `/boards/${cat.boards[0].slug}` : `/boards?category=${cat.slug}`}
|
||||||
className={`block w-full px-2 py-2 text-sm font-medium transition-colors duration-200 hover:text-neutral-900 whitespace-nowrap ${
|
className={`block w-full px-2 py-2 text-sm font-medium transition-colors duration-200 hover:text-neutral-900 whitespace-nowrap ${
|
||||||
activeCategorySlug === cat.slug ? "text-neutral-900" : "text-neutral-700"
|
activeCategorySlug === cat.slug ? "text-neutral-900" : "text-neutral-700"
|
||||||
}`}
|
}`}
|
||||||
@@ -381,11 +381,11 @@ export function AppHeader() {
|
|||||||
{cat.boards.map((b) => (
|
{cat.boards.map((b) => (
|
||||||
<Link
|
<Link
|
||||||
key={b.id}
|
key={b.id}
|
||||||
href={`/boards/${b.id}`}
|
href={`/boards/${b.slug}`}
|
||||||
className={`rounded px-2 py-1 text-sm transition-colors duration-150 hover:bg-neutral-100 hover:text-neutral-900 text-center whitespace-nowrap ${
|
className={`rounded px-2 py-1 text-sm transition-colors duration-150 hover:bg-neutral-100 hover:text-neutral-900 text-center whitespace-nowrap ${
|
||||||
activeBoardId === b.id ? "bg-neutral-100 text-neutral-900 font-medium" : "text-neutral-700"
|
activeBoardId === b.slug ? "bg-neutral-100 text-neutral-900 font-medium" : "text-neutral-700"
|
||||||
}`}
|
}`}
|
||||||
aria-current={activeBoardId === b.id ? "page" : undefined}
|
aria-current={activeBoardId === b.slug ? "page" : undefined}
|
||||||
>
|
>
|
||||||
{b.name}
|
{b.name}
|
||||||
</Link>
|
</Link>
|
||||||
@@ -416,7 +416,7 @@ export function AppHeader() {
|
|||||||
<div className="mb-2 font-semibold text-neutral-800">{cat.name}</div>
|
<div className="mb-2 font-semibold text-neutral-800">{cat.name}</div>
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
{cat.boards.map((b) => (
|
{cat.boards.map((b) => (
|
||||||
<Link key={b.id} href={`/boards/${b.id}`} onClick={() => setMobileOpen(false)} className="rounded px-2 py-1 text-neutral-700 hover:bg-neutral-100 hover:text-neutral-900">
|
<Link key={b.id} href={`/boards/${b.slug}`} onClick={() => setMobileOpen(false)} className="rounded px-2 py-1 text-neutral-700 hover:bg-neutral-100 hover:text-neutral-900">
|
||||||
{b.name}
|
{b.name}
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ type ApiCategory = {
|
|||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
boards: { id: string; name: string; slug: string; requiresApproval: boolean }[];
|
boards: { id: string; name: string; slug: string }[];
|
||||||
};
|
};
|
||||||
|
|
||||||
type PostItem = {
|
type PostItem = {
|
||||||
@@ -101,7 +101,7 @@ export default function CategoryBoardBrowser({ categoryName, categorySlug }: Pro
|
|||||||
className="shrink-0 text-lg md:text-xl font-bold text-neutral-800 truncate"
|
className="shrink-0 text-lg md:text-xl font-bold text-neutral-800 truncate"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const first = selectedCategory?.boards?.[0];
|
const first = selectedCategory?.boards?.[0];
|
||||||
if (first?.id) router.push(`/boards/${first.id}`);
|
if (first?.slug) router.push(`/boards/${first.slug}`);
|
||||||
}}
|
}}
|
||||||
title={(selectedCategory?.name ?? categoryName ?? "").toString()}
|
title={(selectedCategory?.name ?? categoryName ?? "").toString()}
|
||||||
>
|
>
|
||||||
@@ -113,7 +113,7 @@ export default function CategoryBoardBrowser({ categoryName, categorySlug }: Pro
|
|||||||
className="shrink-0 w-6 h-6 rounded-full border border-neutral-300 text-neutral-500 hover:bg-neutral-50 flex items-center justify-center"
|
className="shrink-0 w-6 h-6 rounded-full border border-neutral-300 text-neutral-500 hover:bg-neutral-50 flex items-center justify-center"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const first = selectedCategory?.boards?.[0];
|
const first = selectedCategory?.boards?.[0];
|
||||||
if (first?.id) router.push(`/boards/${first.id}`);
|
if (first?.slug) router.push(`/boards/${first.slug}`);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<svg width="12" height="12" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
<svg width="12" height="12" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { HeroBanner } from "@/app/components/HeroBanner";
|
|||||||
|
|
||||||
export default function EditPostPage() {
|
export default function EditPostPage() {
|
||||||
const params = useParams<{ id: string }>();
|
const params = useParams<{ id: string }>();
|
||||||
const id = params?.id as string;
|
const id = params?.id as string; // slug 값
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { show } = useToast();
|
const { show } = useToast();
|
||||||
const [form, setForm] = useState<{ title: string; content: string } | null>(null);
|
const [form, setForm] = useState<{ title: string; content: string } | null>(null);
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { HeroBanner } from "@/app/components/HeroBanner";
|
|||||||
// 서버 전용 페이지: params가 Promise일 수 있어 안전 언랩 후 절대 URL로 fetch합니다.
|
// 서버 전용 페이지: params가 Promise일 수 있어 안전 언랩 후 절대 URL로 fetch합니다.
|
||||||
export default async function PostDetail({ params }: { params: any }) {
|
export default async function PostDetail({ params }: { params: any }) {
|
||||||
const p = params?.then ? await params : params;
|
const p = params?.then ? await params : params;
|
||||||
const id = p.id as string;
|
const id = p.id as string; // slug 값이 들어옴
|
||||||
const h = await headers();
|
const h = await headers();
|
||||||
const host = h.get("host") ?? "localhost:3000";
|
const host = h.get("host") ?? "localhost:3000";
|
||||||
const proto = h.get("x-forwarded-proto") ?? "http";
|
const proto = h.get("x-forwarded-proto") ?? "http";
|
||||||
|
|||||||
Reference in New Issue
Block a user