diff options
| author | Andrew Lee <andrew@alee14.me> | 2025-03-25 14:13:06 -0400 |
|---|---|---|
| committer | Andrew Lee <andrew@alee14.me> | 2025-03-25 14:13:06 -0400 |
| commit | 1c12d378d66b92b1674acd17640f2bac752da289 (patch) | |
| tree | bc8a1ef5047be1ed2400f2204a0222a840375851 | |
| parent | ad768e2b25b58d62a44aa2daeb1429a651d488e5 (diff) | |
| download | AleeBot-1c12d378d66b92b1674acd17640f2bac752da289.tar.gz AleeBot-1c12d378d66b92b1674acd17640f2bac752da289.tar.bz2 AleeBot-1c12d378d66b92b1674acd17640f2bac752da289.zip | |
Converted public dashboard to admin dashboard; Made API have a consistent output message
| -rw-r--r-- | bot/src/api/routes/auth.js | 14 | ||||
| -rw-r--r-- | bot/src/api/routes/quotes.js | 18 | ||||
| -rw-r--r-- | bot/src/api/routes/settings.js | 10 | ||||
| -rw-r--r-- | bot/src/api/server.js | 4 | ||||
| -rw-r--r-- | bot/src/commands/settings.js | 8 | ||||
| -rw-r--r-- | web/bun.lockb | bin | 154951 -> 151013 bytes | |||
| -rw-r--r-- | web/package.json | 1 | ||||
| -rw-r--r-- | web/src/app/api/auth/[...nextauth]/route.js | 2 | ||||
| -rw-r--r-- | web/src/app/components/Guilds.jsx | 25 | ||||
| -rw-r--r-- | web/src/app/components/Navbar.jsx | 25 | ||||
| -rw-r--r-- | web/src/app/components/sign-in.jsx | 14 | ||||
| -rw-r--r-- | web/src/app/components/sign-out.jsx | 11 | ||||
| -rw-r--r-- | web/src/app/dashboard/page.js | 65 | ||||
| -rw-r--r-- | web/src/app/page.js | 42 | ||||
| -rw-r--r-- | web/src/lib/auth.js | 32 |
15 files changed, 117 insertions, 154 deletions
diff --git a/bot/src/api/routes/auth.js b/bot/src/api/routes/auth.js index 224a2d1..81a3e40 100644 --- a/bot/src/api/routes/auth.js +++ b/bot/src/api/routes/auth.js @@ -6,7 +6,7 @@ import dotenv from 'dotenv'; dotenv.config(); // Check if required environment variables are set -const requiredEnvVars = ['JWT_SECRET', 'AUTH_USERNAME', 'AUTH_PASSWORD_HASH']; +const requiredEnvVars = ['JWT_SECRET', 'API_USERNAME', 'API_PASSWORD_HASH']; const missingVars = requiredEnvVars.filter(varName => !process.env[varName]); if (missingVars.length > 0) { console.error(`Missing required environment variables: ${missingVars.join(', ')}`); @@ -22,18 +22,18 @@ export function authRouter() { const { username, password } = req.body; if (!username || !password) { - return res.status(400).json({ error: 'Username and password are required' }); + return res.status(400).json({ message: 'Username and password are required' }); } // Check against environment variables if (username !== process.env.API_USERNAME) { - return res.status(401).json({ error: 'Invalid credentials' }); + return res.status(401).json({ message: 'Invalid credentials' }); } // Verify password const isPasswordValid = await bcrypt.compare(password, process.env.API_PASSWORD_HASH); if (!isPasswordValid) { - return res.status(401).json({ error: 'Invalid credentials' }); + return res.status(401).json({ message: 'Invalid credentials' }); } // Generate JWT token @@ -46,7 +46,7 @@ export function authRouter() { res.json({ token }); } catch (error) { console.error('Login error:', error); - res.status(500).json({ error: 'Internal server error' }); + res.status(500).json({ message: 'Internal server error' }); } }); @@ -58,7 +58,7 @@ export function verifyToken(req, res, next) { const authHeader = req.headers.authorization; if (!authHeader || !authHeader.startsWith('Bearer ')) { - return res.status(401).json({ error: 'No token provided' }); + return res.status(401).json({ message: 'Unauthorized' }); } const token = authHeader.split(' ')[1]; @@ -68,6 +68,6 @@ export function verifyToken(req, res, next) { req.user = decoded; next(); } catch { - return res.status(403).json({ error: 'Invalid or expired token' }); + return res.status(403).json({ message: 'Invalid or expired token' }); } } diff --git a/bot/src/api/routes/quotes.js b/bot/src/api/routes/quotes.js index 7f9f255..f362d7e 100644 --- a/bot/src/api/routes/quotes.js +++ b/bot/src/api/routes/quotes.js @@ -10,7 +10,7 @@ quoteRouter.get('/quotes/pending', verifyToken, async (req, res) => { res.json(quotes); } catch (error) { console.error('Error fetching quotes:', error); - res.status(500).send('Internal Server Error'); + res.status(500).send({ message: 'Internal Server Error' }); } }); @@ -24,10 +24,10 @@ quoteRouter.post('/quotes/add', verifyToken, async (req, res) => { year: year, submitter: submitterID }); - res.status(200).send('Added a new quote'); + res.status(200).send({ message: 'Added a new quote' }); } catch (error) { console.error('Something went wrong:', error); - res.status(500).send('Internal Server Error'); + res.status(500).send({ message: 'Internal Server Error' }); } }); @@ -44,13 +44,13 @@ quoteRouter.post('/quotes/approve', verifyToken, async (req, res) => { submitter: quote.submitterID }); await pendingQuote.destroy({ where: { id } }); - res.status(200).send('Quote approved'); + res.status(200).send({ message: 'Quote approved' }); } else { - res.status(404).send('Quote not found'); + res.status(404).send({ message: 'Quote not found' }); } } catch (error) { console.error('Error approving quote:', error); - res.status(500).send('Internal Server Error'); + res.status(500).send({ message: 'Internal Server Error' }); } }); @@ -60,12 +60,12 @@ quoteRouter.post('/quotes/reject', verifyToken, async (req, res) => { const quote = await pendingQuote.findByPk(id); if (quote) { await pendingQuote.destroy({ where: { id } }); - res.status(200).send('Quote rejected'); + res.status(200).send({ message: 'Quote rejected' }); } else { - res.status(404).send('Quote not found'); + res.status(404).send({ message: 'Quote not found' }); } } catch (error) { console.error('Error rejecting quote:', error); - res.status(500).send('Internal Server Error'); + res.status(500).send({ message: 'Internal Server Error' }); } }); diff --git a/bot/src/api/routes/settings.js b/bot/src/api/routes/settings.js index bdef633..794d302 100644 --- a/bot/src/api/routes/settings.js +++ b/bot/src/api/routes/settings.js @@ -6,7 +6,7 @@ import { verifyToken } from './auth.js'; export function settingsRouter(client) { const router = Router(); - router.get('/settings/guild/:id', verifyToken, async (req, res) => { + router.get('/settings/guilds/:id', verifyToken, async (req, res) => { try { const settings = await guildSettings.findOne({ where: { guildID: req.params.id } }); @@ -38,11 +38,11 @@ export function settingsRouter(client) { }); } catch (e) { console.error('Error fetching settings:', e); - res.status(500).send('Internal Server Error'); + res.status(500).send({ message: 'Internal Server Error' }); } }); - router.post('/settings/guild/:id', verifyToken, async (req, res) => { + router.post('/settings/guilds/:id', verifyToken, async (req, res) => { try { const guildID = req.params.id; const { ...newSettings } = req.body; @@ -51,11 +51,11 @@ export function settingsRouter(client) { const updatedSettings = await guildSettings.findOne({ where: { guildID: guildID } }); res.json(updatedSettings); } else { - res.status(404).send('Settings not found'); + res.status(404).send({ message: 'Settings not found' }); } } catch (e) { console.error('Error updating settings:', e); - res.status(500).send('Internal Server Error'); + res.status(500).send({ message: 'Internal Server Error' }); } }); diff --git a/bot/src/api/server.js b/bot/src/api/server.js index 9ad2026..0a06523 100644 --- a/bot/src/api/server.js +++ b/bot/src/api/server.js @@ -21,8 +21,8 @@ export const apiServer = (client) => { app.get('/api/version', (req, res) => { const { version } = JSON.parse(readFileSync('./package.json', 'utf-8')); res.json({ - api_version: '1.0', - ab_version: version + ab_version: version, + api_version: '1.1' }); }); diff --git a/bot/src/commands/settings.js b/bot/src/commands/settings.js index 5deb371..cb06c99 100644 --- a/bot/src/commands/settings.js +++ b/bot/src/commands/settings.js @@ -6,6 +6,7 @@ export default { .setName('settings') .setDescription('Settings for AleeBot.') .setContexts(0) + .setDefaultMemberPermissions(PermissionFlagsBits.ManageGuild) .addSubcommand(subcommand => subcommand .setName('set') @@ -38,7 +39,10 @@ export default { if (!interaction.member.permissions.has(PermissionFlagsBits.ManageGuild) && !interaction.member.permissions.has(PermissionFlagsBits.Administrator) && interaction.user.id !== interaction.guild.ownerId) return await interaction.reply({ content: 'You do not have the permission to manage this guild.', flags: MessageFlags.Ephemeral }); + const guildSetting = await guildSettings.findOne({ where: { guildID: interaction.guild.id } }); + if (!guildSetting) await guildSettings.create({ guildID: interaction.guild.id }); + if (interaction.options.getSubcommand() === 'clear') { await guildSettings.update({ logChannelID: null, @@ -55,9 +59,6 @@ export default { .setDescription('Settings for this guild.') .setColor(abEmbedColour); - if (!guildSetting) await guildSettings.create({ guildID: interaction.guild.id }); - - // Handle clearing settings if (areAllSettingsEmpty(interaction)) { guildEmbed.addFields( @@ -71,6 +72,7 @@ export default { } // Process each setting type + guildEmbed.setDescription('Updated this setting.'); await updateChannelSetting(interaction, guildEmbed, 'log', 'logChannelID', 'Logging'); await updateChannelSetting(interaction, guildEmbed, 'suggestion', 'suggestionsChannelID', 'Suggestions'); await updateChannelSetting(interaction, guildEmbed, 'qotd', 'qotdChannelID', 'QOTD Channel'); diff --git a/web/bun.lockb b/web/bun.lockb Binary files differindex 8f54964..adc0d02 100644 --- a/web/bun.lockb +++ b/web/bun.lockb diff --git a/web/package.json b/web/package.json index 8e69990..cafde52 100644 --- a/web/package.json +++ b/web/package.json @@ -10,7 +10,6 @@ }, "dependencies": { "next": "^15.2.3", - "next-auth": "^5.0.0-beta.25", "react": "^19.0.0", "react-dom": "^19.0.0" }, diff --git a/web/src/app/api/auth/[...nextauth]/route.js b/web/src/app/api/auth/[...nextauth]/route.js deleted file mode 100644 index 5951f83..0000000 --- a/web/src/app/api/auth/[...nextauth]/route.js +++ /dev/null @@ -1,2 +0,0 @@ -import { handlers } from "@/lib/auth" -export const { GET, POST } = handlers diff --git a/web/src/app/components/Guilds.jsx b/web/src/app/components/Guilds.jsx deleted file mode 100644 index 38626e4..0000000 --- a/web/src/app/components/Guilds.jsx +++ /dev/null @@ -1,25 +0,0 @@ - -export default async function Guilds({session}) { - - const response = await fetch("https://discord.com/api/users/@me/guilds", { - headers: { - Authorization: `Bearer ${session.accessToken}`, - }, - }); - const guilds = await response.json(); - - const ADMINISTRATOR = 0x0000000000000008; - const MANAGE_GUILD = 0x00000020; - - const filteredGuilds = guilds.filter((guild) => { - // Convert permissions string to a BigInt for bitwise operations - const permissions = BigInt(guild.permissions); - // Check if user is owner, has ADMINISTRATOR or MANAGE_GUILD permissions - return guild.owner || - (permissions & BigInt(ADMINISTRATOR)) === BigInt(ADMINISTRATOR) || - (permissions & BigInt(MANAGE_GUILD)) === BigInt(MANAGE_GUILD); }); - - return filteredGuilds.map((guild) => ( - <div key={guild.id} className="p-1">{guild.name}</div> - )) -} diff --git a/web/src/app/components/Navbar.jsx b/web/src/app/components/Navbar.jsx new file mode 100644 index 0000000..242175c --- /dev/null +++ b/web/src/app/components/Navbar.jsx @@ -0,0 +1,25 @@ +import SignOut from "@/app/components/sign-out"; + +export default function Navbar() { + return ( + <nav className="bg-gray-900 text-white"> + <div className="max-w-screen-xl flex items-center justify-between mx-auto p-4"> + <div className="flex items-center space-x-4"> + <h1 className="text-xl font-medium">AleeBot</h1> + <ul> + <li className="inline-block mx-2">Guilds</li> + <li className="inline-block mx-2">Quotes</li> + <li className="inline-block mx-2">Settings</li> + </ul> + </div> + + <div className="flex items-center space-x-4"> + <span>Uptime: 1 day</span> + <span>API v(version)</span> + <span>4.0.0 Beta</span> + <SignOut /> + </div> + </div> + </nav> + ) +} diff --git a/web/src/app/components/sign-in.jsx b/web/src/app/components/sign-in.jsx deleted file mode 100644 index 3d7142f..0000000 --- a/web/src/app/components/sign-in.jsx +++ /dev/null @@ -1,14 +0,0 @@ -import { signIn } from "@/lib/auth" - -export default function SignIn() { - return ( - <form - action={async () => { - "use server" - await signIn("discord") - }} - > - <button type="submit" className="bg-discord-blurple p-3 rounded-md hover:bg-discord-blurple">Login with Discord</button> - </form> - ) -} diff --git a/web/src/app/components/sign-out.jsx b/web/src/app/components/sign-out.jsx index 69162a4..dd6693d 100644 --- a/web/src/app/components/sign-out.jsx +++ b/web/src/app/components/sign-out.jsx @@ -1,14 +1,5 @@ -import { signOut } from "@/lib/auth" - export default function SignOut() { return ( - <form - action={async () => { - "use server" - await signOut("discord") - }} - > - <button type="submit">Log out</button> - </form> + <button type="submit" className="py-2 px-4 rounded-md text-md bg-red-700 hover:bg-red-500">Sign out</button> ) } diff --git a/web/src/app/dashboard/page.js b/web/src/app/dashboard/page.js index bab3bd8..a252958 100644 --- a/web/src/app/dashboard/page.js +++ b/web/src/app/dashboard/page.js @@ -1,39 +1,44 @@ -import { redirect } from "next/navigation"; -import { auth } from "@/lib/auth"; -import SignOut from "@/app/components/sign-out"; -import Guilds from "@/app/components/Guilds"; - -export default async function Home() { - const session = await auth(); - if (!session) redirect("/"); +import Navbar from "@/app/components/Navbar"; +export default function Dashboard() { return ( <> - <nav className="bg-gray-900 text-white"> - <div className="max-w-screen-xl flex items-center justify-between mx-auto p-4"> - <div className="flex items-center"> - <h1 className="text-xl font-medium">AleeBot</h1> + <Navbar /> + <div className="flex flex-col gap-4 p-12"> + <h1 className="text-3xl">Guilds</h1> + <div className="grid grid-cols-2 md:grid-cols-4 gap-4"> + <div className="p-4 bg-gray-900 rounded-md"> + <h2 className="text-lg font-medium">Server 1</h2> + <p>ID: 23893249843983489 - Members: 30</p> + <span>Leave</span> {/* Add an "are you sure prompt" */} </div> - - <div className="flex items-center space-x-4"> - <p className="text-sm md:text-base">Welcome {session.user?.username}!</p> - <SignOut /> + <div className="p-4 bg-gray-900 rounded-md"> + <h2 className="text-lg font-medium">Server 2</h2> + <p>ID: 23893249843983489 - Members: 30</p> + <span>Leave</span> + </div> + <div className="p-4 bg-gray-900 rounded-md"> + <h2 className="text-lg font-medium">Server 3</h2> + <p>ID: 23893249843983489 - Members: 30</p> + <span>Leave</span> + </div> + <div className="p-4 bg-gray-900 rounded-md"> + <h2 className="text-lg font-medium">Server 4</h2> + <p>ID: 23893249843983489 - Members: 30</p> + <span>Leave</span> + </div> + <div className="p-4 bg-gray-900 rounded-md"> + <h2 className="text-lg font-medium">Server 5</h2> + <p>ID: 23893249843983489 - Members: 30</p> + <span>Leave</span> + </div> + <div className="p-4 bg-gray-900 rounded-md"> + <h2 className="text-lg font-medium">Server 6</h2> + <p>ID: 23893249843983489 - Members: 30</p> + <span>Leave</span> </div> - </div> - </nav> - <div className="flex"> - <div> - <div>Settings</div> - <Guilds session={session} /> - </div> - <div> - <h1 className="text-2xl">Logging</h1> - <h2>Channel 1</h2> - <h2>Channel 2</h2> - <h1 className="text-2xl">Quote of the Day</h1> - <h1 className="text-2xl">LLM Chatbot</h1> </div> </div> </> - ) + ); } diff --git a/web/src/app/page.js b/web/src/app/page.js index 76e5d27..99556e8 100644 --- a/web/src/app/page.js +++ b/web/src/app/page.js @@ -1,19 +1,33 @@ -import { redirect } from "next/navigation"; -import { auth } from "@/lib/auth"; +export default function Home() { -export default async function Home() { - const session = await auth(); - if (session) redirect("/dashboard"); - return ( - <> - <main className="flex justify-center items-center h-screen"> - <form className="flex flex-col gap-4 w-80"> - <input for='username' type='text' placeholder='Username' /> - <input for='password' type='password' placeholder='Password' /> - <input for='api' type='url' placeholder='API URL' /> - <input type="submit" value="Login" /> + return ( + <main className="flex flex-col space-y-5 justify-center items-center h-screen"> + <h1 className="text-4xl font-medium">AleeBot</h1> + <form className="flex flex-col gap-4 w-80"> + <input + name="username" + type="text" + placeholder="Username" + required + /> + <input + name="password" + type="password" + placeholder="Password" + required + /> + <input + name="apiUrl" + type="url" + placeholder="API URL" + required + /> + <button + type="submit" + className="bg-blue-500 hover:bg-blue-700 text-white py-2 px-4 rounded" + >Login + </button> </form> </main> - </> ); } diff --git a/web/src/lib/auth.js b/web/src/lib/auth.js deleted file mode 100644 index 28c34f1..0000000 --- a/web/src/lib/auth.js +++ /dev/null @@ -1,32 +0,0 @@ -import NextAuth from "next-auth" -import Discord from "next-auth/providers/discord" - -export const { handlers, signIn, signOut, auth } = NextAuth({ - providers: [Discord({ - authorization: { - url: "https://discord.com/api/oauth2/authorize", - params: { scope: "identify guilds" }, - } - })], - callbacks: { - async jwt({ token, account }) { - // Persist the OAuth access_token to the token right after sign in - if (account) { - token.accessToken = account.access_token; - } - return token; - }, - async session({ session, token }) { - if (token.accessToken) { - session.user = await fetch('https://discord.com/api/users/@me', { - headers: { - authorization: `Bearer ${token.accessToken}` - } - }).then((r) => r.json()); - session.accessToken = token.accessToken; - } - - return session; - } - } -}) |
