diff --git a/.devcontainer/devcontainer-lock.json b/.devcontainer/devcontainer-lock.json new file mode 100644 index 0000000000..2edb305a48 --- /dev/null +++ b/.devcontainer/devcontainer-lock.json @@ -0,0 +1,24 @@ +{ + "features": { + "ghcr.io/devcontainers-extra/features/pnpm:2": { + "version": "2.0.5", + "resolved": "ghcr.io/devcontainers-extra/features/pnpm@sha256:694c2b6182435c9e9c06f6071728b087c1181b38c18cecf0defe2ab8c11cddd6", + "integrity": "sha256:694c2b6182435c9e9c06f6071728b087c1181b38c18cecf0defe2ab8c11cddd6" + }, + "ghcr.io/devcontainers/features/go:1": { + "version": "1.3.4", + "resolved": "ghcr.io/devcontainers/features/go@sha256:d85e921f91b41340055bb12b325d9d551170ed04b3b832e33530bf42f167c032", + "integrity": "sha256:d85e921f91b41340055bb12b325d9d551170ed04b3b832e33530bf42f167c032" + }, + "ghcr.io/devcontainers/features/java:1": { + "version": "1.8.0", + "resolved": "ghcr.io/devcontainers/features/java@sha256:9663ce0219ff85786e87901ce5f0a59f488edd5f99b46015192cda48468b233a", + "integrity": "sha256:9663ce0219ff85786e87901ce5f0a59f488edd5f99b46015192cda48468b233a" + }, + "ghcr.io/devcontainers/features/terraform:1": { + "version": "1.4.3", + "resolved": "ghcr.io/devcontainers/features/terraform@sha256:503d0888b3a43a32335e08cd4477f8c4629404ad83f6a36f0e0fee7f567c06c7", + "integrity": "sha256:503d0888b3a43a32335e08cd4477f8c4629404ad83f6a36f0e0fee7f567c06c7" + } + } +} diff --git a/apps/backend/libs/auth/src/roles/group-leader.guard.ts b/apps/backend/libs/auth/src/roles/group-leader.guard.ts index 722cdefe84..7bc25deece 100644 --- a/apps/backend/libs/auth/src/roles/group-leader.guard.ts +++ b/apps/backend/libs/auth/src/roles/group-leader.guard.ts @@ -1,4 +1,5 @@ import { + ForbiddenException, Injectable, type CanActivate, type ExecutionContext @@ -54,6 +55,8 @@ export class GroupLeaderGuard implements CanActivate { if (isGroupLeader) { return true } - return false + throw new ForbiddenException( + 'You do not have permission to manage this course' + ) } } diff --git a/apps/frontend/app/admin/course/[courseId]/_components/CourseDetailTabs.tsx b/apps/frontend/app/admin/course/[courseId]/_components/CourseDetailTabs.tsx new file mode 100644 index 0000000000..6555923a65 --- /dev/null +++ b/apps/frontend/app/admin/course/[courseId]/_components/CourseDetailTabs.tsx @@ -0,0 +1,85 @@ +'use client' + +import { cn } from '@/libs/utils' +import Link from 'next/link' +import { usePathname } from 'next/navigation' + +interface CourseDetailTabsProps { + courseId: string + courseCode: string + courseTitle: string +} + +export function CourseDetailTabs({ + courseId, + courseCode, + courseTitle +}: CourseDetailTabsProps) { + const pathname = usePathname() + + const tabs = [ + { name: 'Home', title: 'HOME', href: `/admin/course/${courseId}` }, + { + name: 'Member', + title: 'MEMBER', + description: + "Here's a list of the instructors and students of the course", + href: `/admin/course/${courseId}/user` + }, + { + name: 'Assignment', + title: 'ASSIGNMENT', + description: "Here's a assignment list you made", + href: `/admin/course/${courseId}/assignment` + }, + { + name: 'Exercise', + title: 'EXERCISE', + description: "Here's a exercise list you made", + href: `/admin/course/${courseId}/exercise` + }, + { + name: 'Q&A', + title: 'Question & Answer', + description: + 'Assignment와 Exercise 문제와 관련된 질문과 답변을 제공합니다.', + href: `/admin/course/${courseId}/qna` + } + ] + + const activeTabName = + tabs.find((tab) => pathname === tab.href)?.title || 'HOME' + + return ( + <> +
+ {activeTabName === 'HOME' + ? `[${courseCode}] ${courseTitle}` + : tabs.find((tab) => tab.title === activeTabName)?.description} +
+ +- {activeTabName === 'HOME' - ? `[${courseCode}] ${courseTitle}` - : tabs.find((tab) => tab.title === activeTabName)?.description} -
- -