Skip to content

Commit

Permalink
Save
Browse files Browse the repository at this point in the history
  • Loading branch information
LeegwangYeol committed Feb 6, 2025
1 parent f4e358c commit 3ffcc38
Show file tree
Hide file tree
Showing 6 changed files with 438 additions and 3 deletions.
42 changes: 42 additions & 0 deletions app/api/auth/[...nextauth]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import NextAuth from "next-auth";
import GoogleProvider from "next-auth/providers/google";

const handler = NextAuth({
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
authorization: {
params: {
scope:
"openid email profile https://www.googleapis.com/auth/youtube.force-ssl",
prompt: "consent",
access_type: "offline",
response_type: "code",
},
},
}),
],
callbacks: {
async jwt({ token, account }) {
// OAuth 액세스 토큰을 JWT에 포함
if (account) {
token.accessToken = account.access_token;
}
return token;
},
async session({ session, token }) {
// JWT의 액세스 토큰을 세션에 포함
return {
...session,
accessToken: token.accessToken,
};
},
},
pages: {
signIn: "/test2",
},
secret: process.env.NEXTAUTH_SECRET,
});

export { handler as GET, handler as POST };
9 changes: 6 additions & 3 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import "./globals.css";
import Script from "next/script";
import VantaBackground from "./component/vanta-main-background";
import { ThemeProvider } from "./context/theme-context";
import { Providers } from "./providers";

export const metadata: Metadata = {
title: "武로 緣을 잇다",
Expand Down Expand Up @@ -41,9 +42,11 @@ export default function RootLayout({
</Script>
</head>
<body>
<ThemeProvider>
<VantaBackground>{children}</VantaBackground>
</ThemeProvider>
<Providers>
<ThemeProvider>
<VantaBackground>{children}</VantaBackground>
</ThemeProvider>
</Providers>
</body>
</html>
);
Expand Down
7 changes: 7 additions & 0 deletions app/providers.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"use client";

import { SessionProvider } from "next-auth/react";

export function Providers({ children }: { children: React.ReactNode }) {
return <SessionProvider>{children}</SessionProvider>;
}
162 changes: 162 additions & 0 deletions app/test2/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
"use client";

import { useEffect, useState } from "react";
import { signIn, signOut, useSession } from "next-auth/react";

// 세션 타입 확장
interface CustomSession {
user?: {
email?: string | null;
};
accessToken?: string;
}

export default function YouTubeCommentPage() {
const { data: session, status } = useSession();
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [videoId, setVideoId] = useState(""); // 비디오 ID 입력을 위한 상태
const [comment, setComment] = useState(""); // 댓글 내용 입력을 위한 상태

const executeComment = async () => {
const customSession = session as CustomSession;
if (!customSession?.accessToken) {
setError("Please authenticate first");
return;
}

if (!videoId) {
setError("Please enter a video ID");
return;
}

if (!comment) {
setError("Please enter a comment");
return;
}

setIsLoading(true);
setError(null);

try {
// 먼저 채널 ID 가져오기
const userResponse = await fetch(
"https://www.googleapis.com/youtube/v3/channels?part=id&mine=true",
{
headers: {
Authorization: `Bearer ${customSession.accessToken}`,
},
}
);

if (!userResponse.ok) {
throw new Error("Failed to get channel information");
}

const userData = await userResponse.json();
const channelId = userData.items[0]?.id;

if (!channelId) {
throw new Error("Channel ID not found");
}

// 댓글 작성
const response = await fetch(
"https://www.googleapis.com/youtube/v3/commentThreads?part=snippet",
{
method: "POST",
headers: {
Authorization: `Bearer ${customSession.accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
snippet: {
videoId: videoId,
topLevelComment: {
snippet: {
textOriginal: comment,
},
},
},
}),
}
);

if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error?.message || "Failed to post comment");
}

const data = await response.json();
console.log("Comment posted:", data);
setError(null);
// 성공 후 입력 필드 초기화
setComment("");
setVideoId("");
} catch (error: any) {
setError("Failed to post comment: " + error.message);
} finally {
setIsLoading(false);
}
};

return (
<div className="p-4">
<h1 className="text-2xl font-bold mb-4">YouTube Comment Manager</h1>

{error && (
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
{error}
</div>
)}

<div className="space-y-4">
{status === "loading" ? (
<div>Loading...</div>
) : session ? (
<>
<div className="flex items-center space-x-4">
<span>Signed in as {session.user?.email}</span>
<button
onClick={() => signOut()}
className="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded"
>
Sign Out
</button>
</div>

<div className="space-y-2">
<input
type="text"
value={videoId}
onChange={(e) => setVideoId(e.target.value)}
placeholder="Enter YouTube Video ID"
className="w-full p-2 border rounded"
/>
<textarea
value={comment}
onChange={(e) => setComment(e.target.value)}
placeholder="Enter your comment"
className="w-full p-2 border rounded h-24"
/>
<button
onClick={executeComment}
disabled={isLoading || !videoId || !comment}
className="bg-green-500 hover:bg-green-700 text-white font-bold py-2 px-4 rounded disabled:opacity-50"
>
{isLoading ? "Loading..." : "Post Comment"}
</button>
</div>
</>
) : (
<button
onClick={() => signIn("google", { callbackUrl: "/test2" })}
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
>
Sign in with Google
</button>
)}
</div>
</div>
);
}
Loading

0 comments on commit 3ffcc38

Please sign in to comment.