This commit is contained in:
2026-04-22 18:04:20 +09:00
10 changed files with 42 additions and 61 deletions
+3 -2
View File
@@ -1,7 +1,8 @@
# apps/cs/src/shared/config/env.ts 참고
NEXT_PUBLIC_PORT=3000
NEXT_PUBLIC_PROXY_HOST=http://localhost:3000
SERVER_API_URL=http://192.168.0.102:8000
NEXT_PUBLIC_SITE_ID=CS
NEXT_PUBLIC_SUB_DOMAIN=http://localhost:3000
NEXT_PUBLIC_SERVER_API_URL=http://192.168.0.102:8000
NEXT_PUBLIC_TINY_API_KEY=t9nc4z6inquvvd5pq1vea8wcp7lb01qmqseh6egms9vpaju2
NEXT_PUBLIC_TINY_API_KEY=t9nc4z6inquvvd5pq1vea8wcp7lb01qmqseh6egms9vpaju2
NEXT_PUBLIC_TINY_BASE_URL=
+17 -8
View File
@@ -1,21 +1,25 @@
import { NextRequest, NextResponse } from "next/server";
import {
CLAIM_NAME,
AUTH_USER_ID,
CLAIM_NAME,
PORT,
PROXY_HOST,
SERVER_API_URL,
SITE_ID,
} from "./src/shared/config";
const serverApiUrl =
process.env.NEXT_PUBLIC_SERVER_API_URL ?? "http://localhost:3000";
const serverApiUrl = process.env.SERVER_API_URL ?? "http://localhost:3000";
const linkBackendApiHost = process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST ?? "http://localhost:8080";
const linkBackendApiHost =
process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST ?? "http://localhost:8080";
// CSP 헤더 설정 함수 (img-src만)
function addSecurityHeaders(response: NextResponse): NextResponse {
response.headers.set("Content-Security-Policy", "img-src 'self' data: https: blob:;");
response.headers.set(
"Content-Security-Policy",
"img-src 'self' data: https: blob:;",
);
return response;
}
@@ -66,7 +70,7 @@ export function middleware(req: NextRequest) {
const requestHeaders = new Headers(req.headers);
const token = req.cookies.get(CLAIM_NAME)?.value;
const userId = req.cookies.get(AUTH_USER_ID)?.value;
// /api/link 경로를 제거하고 백엔드 API 경로로 변환
const apiPath = pathname.replace("/api/link", "");
const queryParams = searchParams.toString();
@@ -94,11 +98,16 @@ export function middleware(req: NextRequest) {
logMessages.push("\n--------------------------------\n");
// console.log(logMessages.join("\n"));
const response = NextResponse.next();
return addSecurityHeaders(response);
}
export const config = {
matcher: ["/server/:path*", "/attach-service/:path*", "/api/link/:path*", "/"],
matcher: [
"/server/:path*",
"/attach-service/:path*",
"/api/link/:path*",
"/",
],
};
@@ -1,4 +1,4 @@
import { SERVER_API_URL } from "@/shared/config";
import { TINY_BASE_URL } from "@/shared/config";
export const editorBaseConfig = {
// 폐쇄망 환경에서는 API 키 없이 자체 호스팅 모드 사용
@@ -7,7 +7,7 @@ export const editorBaseConfig = {
licenseKey: "gpl",
init: {
language: "ko_KR",
language_url: '/tinymce/langs/ko_KR.js', // 직접 호스팅한 경우 경로 지정
language_url: "/tinymce/langs/ko_KR.js", // 직접 호스팅한 경우 경로 지정
height: 500,
// 폐쇄망 환경을 위한 설정
base_url: "/tinymce",
@@ -38,7 +38,7 @@ export const editorBaseConfig = {
"undo redo | blocks fontfamily fontsize | bold italic underline strikethrough forecolor backcolor | alignleft aligncenter alignright alignjustify | bullist numlist blockquote | table",
"link image",
],
images_upload_base_path: SERVER_API_URL,
images_upload_base_path: TINY_BASE_URL,
file_picker_types: "image",
// 폐쇄망 환경을 위한 추가 설정
content_css: "/tinymce/skins/content/default/content.min.css",
@@ -43,7 +43,7 @@ const GroupsDialog = memo(
const { data: group } = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.GROUPS, groupId],
queryFn: () => GroupApi.findGroupById(groupId!),
enabled: !!groupId && open, // open 시점 groupId가 있을 때만 상세 조회 API 호출
enabled: !isRegistration,
});
const { mutateAsync: createGroup } = useMutationWrapper({
@@ -92,8 +92,9 @@ const GroupsDialog = memo(
};
useEffect(() => {
if (open) form.reset();
}, [open]);
// 그룹 등록 및 취소 시 폼 초기화
if (isRegistration && !open) form.reset();
}, [groupId, open]);
return (
<Dialog open={open} onOpenChange={setOpen} {...props}>
+7 -1
View File
@@ -16,7 +16,13 @@ type RoleSelectProps = React.ComponentPropsWithoutRef<typeof Select> & {
function RoleSelect({ mngrYn = "N", ...props }: RoleSelectProps) {
const { data: roles } = useQuery({
queryKey: [ENDPOINT.USER_SERVICE.ROLES],
queryFn: () => RoleApi.getRoles(),
queryFn: () =>
RoleApi.getRoles({
keywordType: "useYn",
keyword: "Y",
page: 0,
size: 100,
}),
});
const options = roles?.content.filter((role) => role.mngrYn === mngrYn);
+2 -18
View File
@@ -3,13 +3,8 @@ import { useState } from "react";
import type { Role } from "@/entities/role";
import type { ColumnDef } from "@tanstack/react-table";
import { MappingRoleDialog } from "../ui/mapping-role-dialog";
import { Button } from "@common/components";
export const createColumns = ({
deleteRole,
}: {
deleteRole: (roleId: string) => void;
}): ColumnDef<Role>[] => [
export const createColumns = (): ColumnDef<Role>[] => [
{
id: "no",
header: "NO",
@@ -44,18 +39,7 @@ export const createColumns = ({
{
id: "action",
header: "관리",
cell: ({ row }) => (
<div className="flex items-center justify-center gap-2">
<MappingRoleButton roleId={row.original.roleId} />
<Button
size="sm"
color="red"
onClick={() => deleteRole(row.original.roleId)}
>
삭제
</Button>
</div>
),
cell: ({ row }) => <MappingRoleButton roleId={row.original.roleId} />,
},
];
+1 -13
View File
@@ -24,13 +24,6 @@ function ModifyPage({ roleId }: { roleId: string }) {
},
});
const { mutateAsync: deleteRole } = useMutationWrapper({
mutationFn: () => RoleApi.deleteRole(roleId),
onSuccess: () => {
alert("삭제되었습니다.", () => router.push("/role"));
},
});
const form = useForm<RoleFormData>({
resolver: zodResolver(roleFormSchema),
values: {
@@ -47,12 +40,7 @@ function ModifyPage({ roleId }: { roleId: string }) {
<div className="card-header">
<h3 className="text-2xl font-medium text-label">권한 수정</h3>
</div>
<RoleForm
roleId={roleId}
form={form}
handleSave={updateRole}
handleDelete={deleteRole}
/>
<RoleForm roleId={roleId} form={form} handleSave={updateRole} />
</div>
);
}
-1
View File
@@ -26,7 +26,6 @@ type RoleFormProps = {
roleId?: string;
form: UseFormReturn<RoleFormData>;
handleSave: (data: RoleFormData) => Promise<void>;
handleDelete?: () => Promise<void>;
};
function RoleForm({ roleId, form, handleSave, ...props }: RoleFormProps) {
+3 -11
View File
@@ -7,7 +7,7 @@ import { getCoreRowModel, type PaginationState } from "@tanstack/react-table";
import { Button, Input, Label } from "@common/components";
import { RoleApi, type Role, type RoleParams } from "@/entities/role";
import { ENDPOINT } from "@/shared/config";
import { useAlertStore, useDataTable, useMutationWrapper } from "@/shared/lib";
import { useAlertStore, useDataTable } from "@/shared/lib";
import { createColumns } from "../model/table-columns";
function RolePage() {
@@ -16,6 +16,7 @@ function RolePage() {
const [keyword, setKeyword] = useState("");
const [searchParams, setSearchParams] = useState<RoleParams>({
keywordType: "roleName",
keyword: "",
sort: "regDt,desc",
});
@@ -34,13 +35,6 @@ function RolePage() {
}),
});
const { mutateAsync: deleteRole } = useMutationWrapper({
mutationFn: (roleId: string) => RoleApi.deleteRole(roleId),
onSuccess: () => {
alert("삭제되었습니다.", query.refetch);
},
});
const handleRowClick = (row: Role) => {
router.push(`/role/register/${row.roleId}`);
};
@@ -48,9 +42,7 @@ function RolePage() {
const { tableComponent } = useDataTable({
query,
tableOptions: {
columns: createColumns({
deleteRole,
}),
columns: createColumns(),
state: {
pagination,
},
+2 -1
View File
@@ -1,7 +1,7 @@
export const PORT = process.env.NEXT_PUBLIC_PORT || "3000";
export const PROXY_HOST =
process.env.NEXT_PUBLIC_PROXY_HOST || `http://localhost:${PORT}`;
export const SERVER_API_URL = process.env.NEXT_PUBLIC_SERVER_API_URL;
export const SERVER_API_URL = process.env.SERVER_API_URL;
export const AUTH_LOGIN_REDIRECT_URL =
process.env.NEXT_PUBLIC_AUTH_LOGIN_REDIRECT_URL || "/auth/login";
export const SITE_ID = process.env.NEXT_PUBLIC_SITE_ID || "CS";
@@ -11,4 +11,5 @@ export const ACCESS_TOKEN =
export const REFRESH_TOKEN =
process.env.NEXT_PUBLIC_REFRESH_TOKEN || "refresh-token";
export const AUTH_USER_ID = process.env.NEXT_PUBLIC_AUTH_USER_ID || "token-id";
export const TINY_BASE_URL = process.env.NEXT_PUBLIC_TINY_BASE_URL;
export const COOKIE_DOMAIN = process.env.NEXT_PUBLIC_COOKIE_DOMAIN;