mirror of
https://github.com/aydenjahola/discord-multipurpose-bot.git
synced 2024-11-21 16:25:55 +00:00
add inital auth implementation
This commit is contained in:
parent
3a2fe0ebc1
commit
43b8e55022
10 changed files with 550 additions and 14 deletions
255
dashboard/app/admin/manage/[guildId]/page.tsx
Normal file
255
dashboard/app/admin/manage/[guildId]/page.tsx
Normal file
|
@ -0,0 +1,255 @@
|
|||
"use client";
|
||||
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useEffect, useState } from "react";
|
||||
import axios from "axios";
|
||||
|
||||
interface ServerSetting {
|
||||
_id: string;
|
||||
guildId: string;
|
||||
emailDomains: string[];
|
||||
countingChannelId: string;
|
||||
generalChannelId: string;
|
||||
logChannelId: string;
|
||||
verificationChannelId: string;
|
||||
verifiedRoleName: string;
|
||||
actionItemsChannelId: string;
|
||||
actionItemsTargetChannelId: string;
|
||||
}
|
||||
|
||||
interface Channel {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface GuildData {
|
||||
guildId: string;
|
||||
guildName: string;
|
||||
guildIcon: string | null;
|
||||
settings: ServerSetting | null;
|
||||
channels: Channel[];
|
||||
}
|
||||
|
||||
const getChannelNames = async (
|
||||
guildId: string,
|
||||
channelIds: string[]
|
||||
): Promise<Channel[]> => {
|
||||
const channels: Channel[] = [];
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`/api/discord/channels?guildId=${guildId}`
|
||||
);
|
||||
|
||||
const allChannels: Channel[] = response.data;
|
||||
channelIds.forEach((channelId) => {
|
||||
const channel = allChannels.find((c) => c.id === channelId);
|
||||
if (channel) {
|
||||
channels.push({ id: channel.id, name: channel.name });
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error fetching channels:", error);
|
||||
}
|
||||
return channels;
|
||||
};
|
||||
|
||||
export default function ManageServerPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ guildId: string }>;
|
||||
}) {
|
||||
const router = useRouter();
|
||||
const [guildData, setGuildData] = useState<GuildData | null>(null);
|
||||
const [activeTab, setActiveTab] = useState<string>("settings");
|
||||
|
||||
useEffect(() => {
|
||||
const fetchParams = async () => {
|
||||
if (params) {
|
||||
const resolvedParams = await params;
|
||||
const guildId = resolvedParams.guildId;
|
||||
|
||||
const response = await fetch(`/api/discord/guilds`);
|
||||
const serverSettings = await response.json();
|
||||
|
||||
const guildInfo = serverSettings.find(
|
||||
(setting: ServerSetting) => setting.guildId === guildId
|
||||
);
|
||||
|
||||
if (guildInfo) {
|
||||
const channelIds = [
|
||||
guildInfo.countingChannelId,
|
||||
guildInfo.generalChannelId,
|
||||
guildInfo.logChannelId,
|
||||
guildInfo.verificationChannelId,
|
||||
guildInfo.actionItemsChannelId,
|
||||
guildInfo.actionItemsTargetChannelId,
|
||||
];
|
||||
const channels = await getChannelNames(guildId, channelIds);
|
||||
|
||||
setGuildData({
|
||||
guildId,
|
||||
guildName: guildInfo.guildName,
|
||||
guildIcon: guildInfo.guildIcon,
|
||||
settings: guildInfo,
|
||||
channels,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
fetchParams();
|
||||
}, [params]);
|
||||
|
||||
const renderSettingsContent = () => {
|
||||
if (!guildData?.settings) return <div>No settings found.</div>;
|
||||
|
||||
const {
|
||||
emailDomains,
|
||||
countingChannelId,
|
||||
generalChannelId,
|
||||
logChannelId,
|
||||
verificationChannelId,
|
||||
verifiedRoleName,
|
||||
actionItemsChannelId,
|
||||
actionItemsTargetChannelId,
|
||||
} = guildData.settings;
|
||||
|
||||
const countingChannel =
|
||||
guildData.channels?.find((c) => c.id === countingChannelId)?.name ||
|
||||
countingChannelId;
|
||||
const generalChannel =
|
||||
guildData.channels?.find((c) => c.id === generalChannelId)?.name ||
|
||||
generalChannelId;
|
||||
const logChannel =
|
||||
guildData.channels?.find((c) => c.id === logChannelId)?.name ||
|
||||
logChannelId;
|
||||
const verificationChannel =
|
||||
guildData.channels?.find((c) => c.id === verificationChannelId)?.name ||
|
||||
verificationChannelId;
|
||||
const actionItemsChannel =
|
||||
guildData.channels?.find((c) => c.id === actionItemsChannelId)?.name ||
|
||||
actionItemsChannelId;
|
||||
const actionItemsTargetChannel =
|
||||
guildData.channels?.find((c) => c.id === actionItemsTargetChannelId)
|
||||
?.name || actionItemsTargetChannelId;
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<h2 className="text-2xl font-bold">Current Server Settings</h2>
|
||||
<div>
|
||||
<strong>Guild ID:</strong> {guildData.guildId}
|
||||
</div>
|
||||
<div>
|
||||
<strong>Email Domains:</strong> {emailDomains.join(", ") || "None"}
|
||||
</div>
|
||||
<div>
|
||||
<strong>Counting Channel:</strong> {countingChannel}
|
||||
</div>
|
||||
<div>
|
||||
<strong>General Channel:</strong> {generalChannel}
|
||||
</div>
|
||||
<div>
|
||||
<strong>Log Channel:</strong> {logChannel}
|
||||
</div>
|
||||
<div>
|
||||
<strong>Verification Channel:</strong> {verificationChannel}
|
||||
</div>
|
||||
<div>
|
||||
<strong>Verified Role Name:</strong> {verifiedRoleName}
|
||||
</div>
|
||||
<div>
|
||||
<strong>Action Items Channel:</strong> {actionItemsChannel}
|
||||
</div>
|
||||
<div>
|
||||
<strong>Action Items Target Channel:</strong>{" "}
|
||||
{actionItemsTargetChannel}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const renderContent = () => {
|
||||
switch (activeTab) {
|
||||
case "settings":
|
||||
return renderSettingsContent();
|
||||
case "roles":
|
||||
return <div>Roles management for {guildData?.guildName}</div>;
|
||||
case "channels":
|
||||
return <div>Channels management for {guildData?.guildName}</div>;
|
||||
case "logs":
|
||||
return <div>Logs for {guildData?.guildName}</div>;
|
||||
default:
|
||||
return <div>Select a tab to manage.</div>;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen bg-gray-900 text-gray-200">
|
||||
<aside className="w-64 bg-gray-800 p-4">
|
||||
<h2 className="text-lg font-bold mb-4">Admin Panel</h2>
|
||||
<ul className="space-y-2">
|
||||
<li>
|
||||
<button
|
||||
className={`w-full text-left p-2 rounded hover:bg-gray-700 ${
|
||||
activeTab === "settings" ? "bg-gray-700" : ""
|
||||
}`}
|
||||
onClick={() => setActiveTab("settings")}
|
||||
>
|
||||
Server Settings
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button
|
||||
className={`w-full text-left p-2 rounded hover:bg-gray-700 ${
|
||||
activeTab === "roles" ? "bg-gray-700" : ""
|
||||
}`}
|
||||
onClick={() => setActiveTab("roles")}
|
||||
>
|
||||
Manage Roles
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button
|
||||
className={`w-full text-left p-2 rounded hover:bg-gray-700 ${
|
||||
activeTab === "channels" ? "bg-gray-700" : ""
|
||||
}`}
|
||||
onClick={() => setActiveTab("channels")}
|
||||
>
|
||||
Manage Channels
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button
|
||||
className={`w-full text-left p-2 rounded hover:bg-gray-700 ${
|
||||
activeTab === "logs" ? "bg-gray-700" : ""
|
||||
}`}
|
||||
onClick={() => setActiveTab("logs")}
|
||||
>
|
||||
View Logs
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</aside>
|
||||
|
||||
<main className="flex-1 p-8">
|
||||
<h1 className="text-4xl font-bold mb-4">
|
||||
Manage Server: {guildData ? guildData.guildName : "Loading..."}
|
||||
</h1>
|
||||
{guildData?.guildIcon && (
|
||||
<img
|
||||
src={`https://cdn.discordapp.com/icons/${guildData.guildId}/${guildData.guildIcon}.png`}
|
||||
alt={`${guildData.guildName} icon`}
|
||||
className="w-24 h-24 mb-4"
|
||||
/>
|
||||
)}
|
||||
{renderContent()}
|
||||
<button
|
||||
onClick={() => router.back()}
|
||||
className="mt-4 px-4 py-2 bg-blue-600 rounded hover:bg-blue-500 transition duration-200"
|
||||
>
|
||||
Back
|
||||
</button>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
90
dashboard/app/admin/page.tsx
Normal file
90
dashboard/app/admin/page.tsx
Normal file
|
@ -0,0 +1,90 @@
|
|||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useSession, signOut } from "next-auth/react";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
interface ServerSetting {
|
||||
_id: string;
|
||||
guildId: string;
|
||||
guildName: string;
|
||||
guildIcon: string | null;
|
||||
}
|
||||
|
||||
export default function AdminPage() {
|
||||
const { data: session } = useSession();
|
||||
const [serverSettings, setServerSettings] = useState<ServerSetting[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
if (!session) return;
|
||||
|
||||
const fetchServerSettings = async () => {
|
||||
try {
|
||||
const response = await fetch("/api/discord/guilds");
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to fetch server settings");
|
||||
}
|
||||
const data = await response.json();
|
||||
console.log("Fetched Server Settings:", data);
|
||||
setServerSettings(data);
|
||||
} catch (error) {
|
||||
console.error("Error fetching server settings:", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchServerSettings();
|
||||
}, [session]);
|
||||
|
||||
if (!session) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center min-h-screen bg-gray-900 text-gray-200">
|
||||
<h1 className="text-2xl font-bold">Access Denied</h1>
|
||||
<p>Please sign in to access this page.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const handleServerClick = (guildId: string) => {
|
||||
router.push(`/admin/manage/${guildId}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center min-h-screen bg-gray-900 text-gray-200">
|
||||
<h1 className="text-4xl font-bold mb-4">Welcome to the Admin Page!</h1>
|
||||
<p className="mb-6">You are signed in as: {session.user?.name}</p>
|
||||
<button
|
||||
onClick={() => signOut({ callbackUrl: "/" })}
|
||||
className="px-6 py-2 bg-red-600 rounded hover:bg-red-500 transition duration-200 mb-4"
|
||||
>
|
||||
Sign Out
|
||||
</button>
|
||||
|
||||
{loading ? (
|
||||
<p className="mt-4">Loading server settings...</p>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4 mt-4">
|
||||
{serverSettings.map((setting) => (
|
||||
<div
|
||||
key={setting._id}
|
||||
onClick={() => handleServerClick(setting.guildId)}
|
||||
className="bg-gray-800 p-4 rounded shadow-md cursor-pointer hover:bg-gray-700 transition duration-200 flex flex-col items-center"
|
||||
>
|
||||
{setting.guildIcon && (
|
||||
<img
|
||||
src={`https://cdn.discordapp.com/icons/${setting.guildId}/${setting.guildIcon}.png`}
|
||||
alt={`${setting.guildName} icon`}
|
||||
className="w-16 h-16 rounded-full mb-2"
|
||||
/>
|
||||
)}
|
||||
<h2 className="text-lg font-bold">{setting.guildName}</h2>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
21
dashboard/app/api/auth/[...nextauth]/route.ts
Normal file
21
dashboard/app/api/auth/[...nextauth]/route.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
import NextAuth from "next-auth";
|
||||
import DiscordProvider from "next-auth/providers/discord";
|
||||
|
||||
export const authOptions = {
|
||||
providers: [
|
||||
DiscordProvider({
|
||||
clientId: process.env.DISCORD_CLIENT_ID!,
|
||||
clientSecret: process.env.DISCORD_CLIENT_SECRET!,
|
||||
authorization: {
|
||||
params: {
|
||||
scope: "identify guilds",
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
secret: process.env.NEXTAUTH_SECRET,
|
||||
};
|
||||
|
||||
const handler = NextAuth(authOptions);
|
||||
|
||||
export { handler as GET, handler as POST };
|
44
dashboard/app/api/discord/channels/route.ts
Normal file
44
dashboard/app/api/discord/channels/route.ts
Normal file
|
@ -0,0 +1,44 @@
|
|||
import { NextResponse } from "next/server";
|
||||
import axios from "axios";
|
||||
import { getServerSession } from "next-auth/next";
|
||||
import { authOptions } from "../../auth/[...nextauth]/route";
|
||||
|
||||
const DISCORD_API_BASE = "https://discord.com/api/v10";
|
||||
const BOT_TOKEN = process.env.DISCORD_BOT_TOKEN;
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const session = await getServerSession(authOptions);
|
||||
|
||||
if (!session) {
|
||||
return NextResponse.json(
|
||||
{ error: "Unauthorized access." },
|
||||
{ status: 401 }
|
||||
);
|
||||
}
|
||||
|
||||
const url = new URL(request.url);
|
||||
const guildId = url.searchParams.get("guildId");
|
||||
|
||||
if (!guildId || !BOT_TOKEN) {
|
||||
return NextResponse.json(
|
||||
{ error: "Guild ID or Discord bot token not set." },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`${DISCORD_API_BASE}/guilds/${guildId}/channels`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bot ${BOT_TOKEN}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
return NextResponse.json(response.data);
|
||||
} catch (error) {
|
||||
console.error("Error fetching channels from Discord API:", error);
|
||||
return NextResponse.error();
|
||||
}
|
||||
}
|
74
dashboard/app/api/discord/guilds/route.ts
Normal file
74
dashboard/app/api/discord/guilds/route.ts
Normal file
|
@ -0,0 +1,74 @@
|
|||
import { NextResponse } from "next/server";
|
||||
import clientPromise from "@/lib/mongodb";
|
||||
import axios from "axios";
|
||||
|
||||
interface ServerSetting {
|
||||
_id: string;
|
||||
guildId: string;
|
||||
emailDomains: string[];
|
||||
countingChannelId: string;
|
||||
generalChannelId: string;
|
||||
logChannelId: string;
|
||||
verificationChannelId: string;
|
||||
verifiedRoleName: string;
|
||||
actionItemsChannelId: string;
|
||||
actionItemsTargetChannelId: string;
|
||||
}
|
||||
|
||||
const DISCORD_API_BASE = "https://discord.com/api/v10";
|
||||
const BOT_TOKEN = process.env.DISCORD_BOT_TOKEN;
|
||||
|
||||
export async function GET() {
|
||||
if (!BOT_TOKEN) {
|
||||
return NextResponse.json(
|
||||
{ error: "Discord bot token not set." },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const client = await clientPromise;
|
||||
const database = client.db("test");
|
||||
const collection = database.collection<ServerSetting>("serversettings");
|
||||
|
||||
const serverSettings = await collection.find({}).toArray();
|
||||
|
||||
const enrichedServerSettings = await Promise.all(
|
||||
serverSettings.map(async (setting) => {
|
||||
try {
|
||||
const guildResponse = await axios.get(
|
||||
`${DISCORD_API_BASE}/guilds/${setting.guildId}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bot ${BOT_TOKEN}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const guildData = guildResponse.data;
|
||||
|
||||
return {
|
||||
...setting,
|
||||
guildName: guildData.name,
|
||||
guildIcon: guildData.icon,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Error fetching guild details for ${setting.guildId}:`,
|
||||
error
|
||||
);
|
||||
return {
|
||||
...setting,
|
||||
guildName: "Unknown Guild",
|
||||
guildIcon: null,
|
||||
};
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
return NextResponse.json(enrichedServerSettings);
|
||||
} catch (error) {
|
||||
console.error("Error fetching server settings:", error);
|
||||
return NextResponse.error();
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ import localFont from "next/font/local";
|
|||
import "./globals.css";
|
||||
import Navbar from "@/components/Navbar";
|
||||
import Footer from "@/components/Footer";
|
||||
import ClientProvider from "@/components/ClientProvider";
|
||||
|
||||
const geistSans = localFont({
|
||||
src: "./fonts/GeistVF.woff",
|
||||
|
@ -26,14 +27,16 @@ export default function RootLayout({
|
|||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||
>
|
||||
<Navbar />
|
||||
{children}
|
||||
<Footer />
|
||||
</body>
|
||||
</html>
|
||||
<ClientProvider>
|
||||
<html lang="en">
|
||||
<body
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||
>
|
||||
<Navbar />
|
||||
{children}
|
||||
<Footer />
|
||||
</body>
|
||||
</html>
|
||||
</ClientProvider>
|
||||
);
|
||||
}
|
||||
|
|
24
dashboard/app/signin/page.tsx
Normal file
24
dashboard/app/signin/page.tsx
Normal file
|
@ -0,0 +1,24 @@
|
|||
"use client";
|
||||
|
||||
import { signIn } from "next-auth/react";
|
||||
|
||||
export default function SignIn() {
|
||||
const handleSignIn = () => {
|
||||
signIn("discord", { callbackUrl: "/admin" });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center min-h-screen bg-gray-900 text-gray-200">
|
||||
<h1 className="text-4xl font-bold mb-4">Sign In</h1>
|
||||
<p className="mb-6">
|
||||
Please sign in to access the features of our Discord bot.
|
||||
</p>
|
||||
<button
|
||||
onClick={handleSignIn}
|
||||
className="px-6 py-2 bg-blue-600 rounded hover:bg-blue-500 transition duration-200"
|
||||
>
|
||||
Sign in with Discord
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
11
dashboard/components/ClientProvider.tsx
Normal file
11
dashboard/components/ClientProvider.tsx
Normal file
|
@ -0,0 +1,11 @@
|
|||
"use client";
|
||||
|
||||
import { SessionProvider } from "next-auth/react";
|
||||
|
||||
interface ClientProviderProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export default function ClientProvider({ children }: ClientProviderProps) {
|
||||
return <SessionProvider>{children}</SessionProvider>;
|
||||
}
|
10
dashboard/lib/mongodb.ts
Normal file
10
dashboard/lib/mongodb.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
import { MongoClient } from "mongodb";
|
||||
|
||||
if (!process.env.MONGODB_URI) {
|
||||
throw new Error("MongoDB URI not found in .env.local");
|
||||
}
|
||||
|
||||
const client = new MongoClient(process.env.MONGODB_URI);
|
||||
const clientPromise = client.connect();
|
||||
|
||||
export default clientPromise;
|
|
@ -9,18 +9,22 @@
|
|||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/next-auth": "^3.13.0",
|
||||
"axios": "^1.7.7",
|
||||
"mongodb": "^6.10.0",
|
||||
"next": "15.0.1",
|
||||
"next-auth": "^4.24.10",
|
||||
"react": "19.0.0-rc-69d4b800-20241021",
|
||||
"react-dom": "19.0.0-rc-69d4b800-20241021",
|
||||
"next": "15.0.1"
|
||||
"react-dom": "19.0.0-rc-69d4b800-20241021"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "15.0.1",
|
||||
"postcss": "^8",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "15.0.1"
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue