@developer.krd/discord-dashboard 0.1.5 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -46,7 +46,8 @@ var UserSchema = import_elysia.t.Object({
46
46
  username: import_elysia.t.String(),
47
47
  discriminator: import_elysia.t.String(),
48
48
  avatar: import_elysia.t.Nullable(import_elysia.t.String()),
49
- global_name: import_elysia.t.Optional(import_elysia.t.Nullable(import_elysia.t.String()))
49
+ global_name: import_elysia.t.Optional(import_elysia.t.Nullable(import_elysia.t.String())),
50
+ avatarUrl: import_elysia.t.Optional(import_elysia.t.Nullable(import_elysia.t.String()))
50
51
  });
51
52
  var GuildSchema = import_elysia.t.Object({
52
53
  id: import_elysia.t.String(),
@@ -55,8 +56,8 @@ var GuildSchema = import_elysia.t.Object({
55
56
  owner: import_elysia.t.Boolean(),
56
57
  permissions: import_elysia.t.String(),
57
58
  iconUrl: import_elysia.t.Optional(import_elysia.t.Nullable(import_elysia.t.String())),
58
- botInGuild: import_elysia.t.Optional(import_elysia.t.Nullable(import_elysia.t.Boolean())),
59
- inviteUrl: import_elysia.t.Optional(import_elysia.t.Nullable(import_elysia.t.String()))
59
+ botInGuild: import_elysia.t.Optional(import_elysia.t.Boolean()),
60
+ inviteUrl: import_elysia.t.Optional(import_elysia.t.String())
60
61
  });
61
62
  var SessionSchema = import_elysia.t.Object({
62
63
  oauthState: import_elysia.t.Optional(import_elysia.t.String()),
@@ -2435,54 +2436,183 @@ function createElysiaAdapter(options) {
2435
2436
  const basePath = options.basePath ?? "/dashboard";
2436
2437
  const sessionName = options.sessionName ?? "discord_dashboard.sid";
2437
2438
  const app = options.app ?? new import_elysia2.Elysia({ adapter: (0, import_node.default)() });
2439
+ const getSession = async (sessionSigner, cookie) => {
2440
+ const cookieValue = cookie[sessionName]?.value;
2441
+ if (!cookieValue) return null;
2442
+ const session2 = await sessionSigner.verify(cookieValue);
2443
+ return session2 ?? null;
2444
+ };
2445
+ const setSession = async (sessionSigner, cookie, session2) => {
2446
+ const token = await sessionSigner.sign(session2);
2447
+ cookie[sessionName].set({
2448
+ value: token,
2449
+ httpOnly: true,
2450
+ sameSite: "lax",
2451
+ path: "/"
2452
+ });
2453
+ };
2454
+ const buildContext = (session2, query) => ({
2455
+ user: session2.discordAuth.user,
2456
+ guilds: (session2.discordAuth.guilds || []).map((guild) => ({
2457
+ ...guild,
2458
+ botInGuild: guild.botInGuild ?? void 0,
2459
+ inviteUrl: guild.inviteUrl ?? void 0
2460
+ })),
2461
+ accessToken: session2.discordAuth.accessToken || "",
2462
+ selectedGuildId: typeof query?.guildId === "string" ? query.guildId : void 0,
2463
+ helpers: engine.helpers
2464
+ });
2438
2465
  const router = new import_elysia2.Elysia({ prefix: basePath }).use(
2439
2466
  (0, import_jwt.default)({
2440
2467
  name: "sessionSigner",
2441
2468
  secret: options.sessionSecret,
2442
2469
  schema: SessionSchema
2443
2470
  })
2444
- ).derive(({ set }) => ({
2445
- html: (content) => {
2446
- set.headers["Content-Type"] = "text/html; charset=utf8";
2447
- return content;
2448
- }
2449
- })).get("/", async ({ sessionSigner, cookie, redirect, html }) => {
2450
- const cookieValue = cookie[sessionName].value;
2451
- if (!cookieValue) return redirect(`${basePath}/login`);
2452
- const sessionData = await sessionSigner.verify(cookieValue);
2471
+ ).get("/", async ({ sessionSigner, cookie, redirect }) => {
2472
+ const sessionData = await getSession(sessionSigner, cookie);
2453
2473
  if (!sessionData || !sessionData.discordAuth) return redirect(`${basePath}/login`);
2454
- return html(engine.render(basePath));
2474
+ return new Response(engine.render(basePath), {
2475
+ headers: {
2476
+ "content-type": "text/html; charset=utf-8"
2477
+ }
2478
+ });
2455
2479
  }).get("/login", async ({ sessionSigner, cookie, redirect }) => {
2456
2480
  const state = (0, import_node_crypto2.randomBytes)(16).toString("hex");
2457
- const token = await sessionSigner.sign({ oauthState: state });
2458
- cookie[sessionName].set({
2459
- value: token,
2460
- httpOnly: true,
2461
- path: "/"
2462
- });
2481
+ await setSession(sessionSigner, cookie, { oauthState: state });
2463
2482
  return redirect(engine.getAuthUrl(state));
2464
2483
  }).get("/callback", async ({ query, sessionSigner, cookie, redirect }) => {
2465
2484
  const { code, state } = query;
2466
- const cookieValue = cookie[sessionName].value;
2467
- if (!cookieValue) return redirect(`${basePath}/login`);
2468
- const session2 = await sessionSigner.verify(cookieValue);
2469
- if (!state || !session2 || state !== session2.oauthState) return redirect(`${basePath}/login`);
2470
- const tokens = await engine.exchangeCode(code);
2471
- const [user, guilds] = await Promise.all([engine.fetchUser(tokens.access_token), engine.fetchGuilds(tokens.access_token)]);
2472
- const token = await sessionSigner.sign({
2473
- oauthState: void 0,
2474
- discordAuth: {
2475
- accessToken: tokens.access_token,
2476
- user,
2477
- guilds
2478
- }
2479
- });
2480
- cookie[sessionName].set({
2481
- value: token,
2482
- httpOnly: true,
2483
- path: "/"
2484
- });
2485
- return redirect(basePath);
2485
+ const session2 = await getSession(sessionSigner, cookie);
2486
+ if (!code || !state || !session2 || state !== session2.oauthState) return redirect(`${basePath}/login`);
2487
+ try {
2488
+ const tokens = await engine.exchangeCode(code);
2489
+ const [user, rawGuilds] = await Promise.all([engine.fetchUser(tokens.access_token), engine.fetchGuilds(tokens.access_token)]);
2490
+ const ADMIN = 8n;
2491
+ const MANAGE_GUILD = 32n;
2492
+ const processedGuilds = rawGuilds.filter((guild) => {
2493
+ const perms = BigInt(guild.permissions || "0");
2494
+ return (perms & ADMIN) === ADMIN || (perms & MANAGE_GUILD) === MANAGE_GUILD;
2495
+ }).map((guild) => ({
2496
+ id: guild.id,
2497
+ name: guild.name,
2498
+ icon: guild.icon,
2499
+ owner: guild.owner,
2500
+ permissions: guild.permissions,
2501
+ iconUrl: engine.helpers.getGuildIconUrl(guild.id, guild.icon),
2502
+ botInGuild: options.client.guilds.cache.has(guild.id)
2503
+ }));
2504
+ await setSession(sessionSigner, cookie, {
2505
+ discordAuth: {
2506
+ accessToken: tokens.access_token,
2507
+ user: {
2508
+ id: user.id,
2509
+ username: user.username,
2510
+ discriminator: user.discriminator,
2511
+ avatar: user.avatar,
2512
+ global_name: user.global_name,
2513
+ avatarUrl: engine.helpers.getUserAvatarUrl(user.id, user.avatar)
2514
+ },
2515
+ guilds: processedGuilds
2516
+ }
2517
+ });
2518
+ return redirect(basePath);
2519
+ } catch (error) {
2520
+ console.error("Dashboard Auth Error:", error);
2521
+ return redirect(`${basePath}/login`);
2522
+ }
2523
+ }).get("/api/session", async ({ sessionSigner, cookie }) => {
2524
+ const session2 = await getSession(sessionSigner, cookie);
2525
+ if (!session2?.discordAuth) return { authenticated: false };
2526
+ return {
2527
+ authenticated: true,
2528
+ user: session2.discordAuth.user,
2529
+ guildCount: session2.discordAuth.guilds.length
2530
+ };
2531
+ }).get("/api/guilds", async ({ sessionSigner, cookie, set }) => {
2532
+ const session2 = await getSession(sessionSigner, cookie);
2533
+ if (!session2?.discordAuth) {
2534
+ set.status = 401;
2535
+ return { error: "Unauthorized" };
2536
+ }
2537
+ return { guilds: session2.discordAuth.guilds };
2538
+ }).get("/api/overview", async ({ sessionSigner, cookie, query, set }) => {
2539
+ const session2 = await getSession(sessionSigner, cookie);
2540
+ if (!session2?.discordAuth) {
2541
+ set.status = 401;
2542
+ return { error: "Unauthorized" };
2543
+ }
2544
+ const cards = options.getOverviewCards ? await options.getOverviewCards(buildContext(session2, query)) : [];
2545
+ return { cards };
2546
+ }).get("/api/home/categories", async ({ sessionSigner, cookie, query, set }) => {
2547
+ const session2 = await getSession(sessionSigner, cookie);
2548
+ if (!session2?.discordAuth) {
2549
+ set.status = 401;
2550
+ return { error: "Unauthorized" };
2551
+ }
2552
+ const categories = options.home?.getCategories ? await options.home.getCategories(buildContext(session2, query)) : [];
2553
+ return { categories };
2554
+ }).get("/api/home", async ({ sessionSigner, cookie, query, set }) => {
2555
+ const session2 = await getSession(sessionSigner, cookie);
2556
+ if (!session2?.discordAuth) {
2557
+ set.status = 401;
2558
+ return { error: "Unauthorized" };
2559
+ }
2560
+ const context = buildContext(session2, query);
2561
+ const sections = options.home?.getSections ? await options.home.getSections(context) : [];
2562
+ const categoryId = typeof query.categoryId === "string" ? query.categoryId : void 0;
2563
+ const filteredSections = categoryId ? sections.filter((section) => section.categoryId === categoryId) : sections;
2564
+ return { sections: filteredSections };
2565
+ }).post("/api/home/:actionId", async ({ params, body, sessionSigner, cookie, query, set }) => {
2566
+ const session2 = await getSession(sessionSigner, cookie);
2567
+ if (!session2?.discordAuth) {
2568
+ set.status = 401;
2569
+ return { error: "Unauthorized" };
2570
+ }
2571
+ const actionFn = options.home?.actions?.[params.actionId];
2572
+ if (!actionFn) {
2573
+ set.status = 404;
2574
+ return { error: "Action not found" };
2575
+ }
2576
+ try {
2577
+ return await actionFn(buildContext(session2, query), body);
2578
+ } catch (error) {
2579
+ set.status = 500;
2580
+ return { error: error instanceof Error ? error.message : "Action failed" };
2581
+ }
2582
+ }).get("/api/plugins", async ({ sessionSigner, cookie, query, set }) => {
2583
+ const session2 = await getSession(sessionSigner, cookie);
2584
+ if (!session2?.discordAuth) {
2585
+ set.status = 401;
2586
+ return { error: "Unauthorized" };
2587
+ }
2588
+ const context = buildContext(session2, query);
2589
+ const resolvedPlugins = await Promise.all(
2590
+ (options.plugins || []).map(async (plugin) => ({
2591
+ id: plugin.id,
2592
+ name: plugin.name,
2593
+ description: plugin.description,
2594
+ panels: plugin.getPanels ? await plugin.getPanels(context) : plugin.panels || []
2595
+ }))
2596
+ );
2597
+ return { plugins: resolvedPlugins };
2598
+ }).post("/api/plugins/:pluginId/:actionId", async ({ params, body, sessionSigner, cookie, query, set }) => {
2599
+ const session2 = await getSession(sessionSigner, cookie);
2600
+ if (!session2?.discordAuth) {
2601
+ set.status = 401;
2602
+ return { error: "Unauthorized" };
2603
+ }
2604
+ const plugin = options.plugins?.find((item) => item.id === params.pluginId);
2605
+ if (!plugin || !plugin.actions?.[params.actionId]) {
2606
+ set.status = 404;
2607
+ return { error: "Plugin or action not found" };
2608
+ }
2609
+ try {
2610
+ return await plugin.actions[params.actionId](buildContext(session2, query), body);
2611
+ } catch (error) {
2612
+ console.error(`Action Error (${params.pluginId}/${params.actionId}):`, error);
2613
+ set.status = 500;
2614
+ return { error: "Internal Server Error" };
2615
+ }
2486
2616
  });
2487
2617
  app.use(router);
2488
2618
  return { app, engine };
@@ -2493,6 +2623,16 @@ var import_crypto = require("crypto");
2493
2623
  function createFastifyAdapter(fastify, options) {
2494
2624
  const engine = new DashboardEngine(options);
2495
2625
  const basePath = options.basePath ?? "/dashboard";
2626
+ const buildContext = (request) => {
2627
+ const query = request.query;
2628
+ return {
2629
+ user: request.session.discordAuth.user,
2630
+ guilds: request.session.discordAuth.guilds || [],
2631
+ accessToken: request.session.discordAuth.accessToken || "",
2632
+ selectedGuildId: typeof query.guildId === "string" ? query.guildId : void 0,
2633
+ helpers: engine.helpers
2634
+ };
2635
+ };
2496
2636
  fastify.register(
2497
2637
  async (instance) => {
2498
2638
  instance.get("/", async (request, reply) => {
@@ -2507,15 +2647,123 @@ function createFastifyAdapter(fastify, options) {
2507
2647
  instance.get("/callback", async (request, reply) => {
2508
2648
  const { code, state } = request.query;
2509
2649
  if (state !== request.session.oauthState) return reply.status(403).send("Invalid OAuth2 state");
2510
- const tokens = await engine.exchangeCode(code);
2511
- const [user, guilds] = await Promise.all([engine.fetchUser(tokens.access_token), engine.fetchGuilds(tokens.access_token)]);
2512
- request.session.discordAuth = {
2513
- accessToken: tokens.access_token,
2514
- user,
2515
- guilds
2516
- };
2517
- return reply.redirect(basePath);
2650
+ try {
2651
+ const tokens = await engine.exchangeCode(code);
2652
+ const [user, rawGuilds] = await Promise.all([engine.fetchUser(tokens.access_token), engine.fetchGuilds(tokens.access_token)]);
2653
+ const ADMIN = 8n;
2654
+ const MANAGE_GUILD = 32n;
2655
+ const processedGuilds = rawGuilds.filter((guild) => {
2656
+ const perms = BigInt(guild.permissions || "0");
2657
+ return (perms & ADMIN) === ADMIN || (perms & MANAGE_GUILD) === MANAGE_GUILD;
2658
+ }).map((guild) => ({
2659
+ ...guild,
2660
+ iconUrl: engine.helpers.getGuildIconUrl(guild.id, guild.icon),
2661
+ botInGuild: options.client.guilds.cache.has(guild.id)
2662
+ }));
2663
+ request.session.discordAuth = {
2664
+ accessToken: tokens.access_token,
2665
+ user: {
2666
+ ...user,
2667
+ avatarUrl: engine.helpers.getUserAvatarUrl(user.id, user.avatar)
2668
+ },
2669
+ guilds: processedGuilds
2670
+ };
2671
+ return reply.redirect(basePath);
2672
+ } catch (error) {
2673
+ console.error("Dashboard Auth Error:", error);
2674
+ return reply.redirect(`${basePath}/login`);
2675
+ }
2676
+ });
2677
+ instance.get("/api/session", async (request, reply) => {
2678
+ if (!request.session.discordAuth) {
2679
+ return reply.send({ authenticated: false });
2680
+ }
2681
+ return reply.send({
2682
+ authenticated: true,
2683
+ user: request.session.discordAuth.user,
2684
+ guildCount: request.session.discordAuth.guilds.length
2685
+ });
2686
+ });
2687
+ instance.get("/api/guilds", async (request, reply) => {
2688
+ if (!request.session.discordAuth) {
2689
+ return reply.status(401).send({ error: "Unauthorized" });
2690
+ }
2691
+ return reply.send({ guilds: request.session.discordAuth.guilds });
2692
+ });
2693
+ instance.get("/api/overview", async (request, reply) => {
2694
+ if (!request.session.discordAuth) {
2695
+ return reply.status(401).send({ error: "Unauthorized" });
2696
+ }
2697
+ const cards = options.getOverviewCards ? await options.getOverviewCards(buildContext(request)) : [];
2698
+ return reply.send({ cards });
2699
+ });
2700
+ instance.get("/api/home/categories", async (request, reply) => {
2701
+ if (!request.session.discordAuth) {
2702
+ return reply.status(401).send({ error: "Unauthorized" });
2703
+ }
2704
+ const categories = options.home?.getCategories ? await options.home.getCategories(buildContext(request)) : [];
2705
+ return reply.send({ categories });
2706
+ });
2707
+ instance.get("/api/home", async (request, reply) => {
2708
+ if (!request.session.discordAuth) {
2709
+ return reply.status(401).send({ error: "Unauthorized" });
2710
+ }
2711
+ const sections = options.home?.getSections ? await options.home.getSections(buildContext(request)) : [];
2712
+ const query = request.query;
2713
+ const categoryId = typeof query.categoryId === "string" ? query.categoryId : void 0;
2714
+ const filteredSections = categoryId ? sections.filter((section) => section.categoryId === categoryId) : sections;
2715
+ return reply.send({ sections: filteredSections });
2716
+ });
2717
+ instance.post("/api/home/:actionId", async (request, reply) => {
2718
+ if (!request.session.discordAuth) {
2719
+ return reply.status(401).send({ error: "Unauthorized" });
2720
+ }
2721
+ const actionFn = options.home?.actions?.[request.params.actionId];
2722
+ if (!actionFn) {
2723
+ return reply.status(404).send({ error: "Action not found" });
2724
+ }
2725
+ try {
2726
+ const result = await actionFn(buildContext(request), request.body);
2727
+ return reply.send(result);
2728
+ } catch (error) {
2729
+ return reply.status(500).send({ error: error instanceof Error ? error.message : "Action failed" });
2730
+ }
2731
+ });
2732
+ instance.get("/api/plugins", async (request, reply) => {
2733
+ if (!request.session.discordAuth) {
2734
+ return reply.status(401).send({ error: "Unauthorized" });
2735
+ }
2736
+ const context = buildContext(request);
2737
+ const resolvedPlugins = await Promise.all(
2738
+ (options.plugins || []).map(async (plugin) => ({
2739
+ id: plugin.id,
2740
+ name: plugin.name,
2741
+ description: plugin.description,
2742
+ panels: plugin.getPanels ? await plugin.getPanels(context) : plugin.panels || []
2743
+ }))
2744
+ );
2745
+ return reply.send({ plugins: resolvedPlugins });
2518
2746
  });
2747
+ instance.post(
2748
+ "/api/plugins/:pluginId/:actionId",
2749
+ async (request, reply) => {
2750
+ if (!request.session.discordAuth) {
2751
+ return reply.status(401).send({ error: "Unauthorized" });
2752
+ }
2753
+ const { pluginId, actionId } = request.params;
2754
+ const plugin = options.plugins?.find((item) => item.id === pluginId);
2755
+ if (!plugin || !plugin.actions?.[actionId]) {
2756
+ return reply.status(404).send({ error: "Plugin or action not found" });
2757
+ }
2758
+ try {
2759
+ const result = await plugin.actions[actionId](buildContext(request), request.body);
2760
+ return reply.send(result);
2761
+ } catch (error) {
2762
+ console.error(`Action Error (${pluginId}/${actionId}):`, error);
2763
+ return reply.status(500).send({ error: "Internal Server Error" });
2764
+ }
2765
+ }
2766
+ );
2519
2767
  },
2520
2768
  { prefix: basePath }
2521
2769
  );