aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew Lee <andrew@alee14.me>2025-03-25 14:13:06 -0400
committerAndrew Lee <andrew@alee14.me>2025-03-25 14:13:06 -0400
commit1c12d378d66b92b1674acd17640f2bac752da289 (patch)
treebc8a1ef5047be1ed2400f2204a0222a840375851
parentad768e2b25b58d62a44aa2daeb1429a651d488e5 (diff)
downloadAleeBot-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.js14
-rw-r--r--bot/src/api/routes/quotes.js18
-rw-r--r--bot/src/api/routes/settings.js10
-rw-r--r--bot/src/api/server.js4
-rw-r--r--bot/src/commands/settings.js8
-rw-r--r--web/bun.lockbbin154951 -> 151013 bytes
-rw-r--r--web/package.json1
-rw-r--r--web/src/app/api/auth/[...nextauth]/route.js2
-rw-r--r--web/src/app/components/Guilds.jsx25
-rw-r--r--web/src/app/components/Navbar.jsx25
-rw-r--r--web/src/app/components/sign-in.jsx14
-rw-r--r--web/src/app/components/sign-out.jsx11
-rw-r--r--web/src/app/dashboard/page.js65
-rw-r--r--web/src/app/page.js42
-rw-r--r--web/src/lib/auth.js32
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
index 8f54964..adc0d02 100644
--- a/web/bun.lockb
+++ b/web/bun.lockb
Binary files differ
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;
- }
- }
-})