Skip to content

Commit cadb766

Browse files
committed
chore: massively improve performance
1 parent a967484 commit cadb766

File tree

9 files changed

+117
-94
lines changed

9 files changed

+117
-94
lines changed

api/src/guilds/mod.rs

Lines changed: 9 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ use http::StatusCode;
66
use lambda_http::tracing::warn;
77
use serde_json::{Value, json};
88
use std::sync::Arc;
9-
use twilight_model::guild::Permissions;
109
use twilight_model::id::Id;
1110
use twilight_model::id::marker::GuildMarker;
1211

1312
mod models;
1413
pub mod verify;
14+
mod utils;
1515

1616
pub fn router() -> Router<AppState> {
1717
Router::new()
@@ -24,19 +24,11 @@ async fn get_guilds(
2424
Extension(discord_user): Extension<Arc<twilight_http::Client>>,
2525
State(app_state): State<AppState>,
2626
) -> Result<Json<Value>, StatusCode> {
27-
let user_guilds = discord_user
28-
.current_user_guilds()
29-
.await
30-
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
31-
.models()
32-
.await
33-
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
34-
let user_guilds_filtered = user_guilds
35-
.iter()
36-
.filter(|g| g.permissions.contains(Permissions::ADMINISTRATOR))
37-
.collect::<Vec<_>>();
3827
let guilds = Guild::vec_from_db(
39-
user_guilds_filtered
28+
utils::intersect_admin_guilds(
29+
&discord_user,
30+
&app_state.discord_bot,
31+
).await
4032
.iter()
4133
.map(|g| g.id.to_string())
4234
.collect(),
@@ -46,60 +38,15 @@ async fn get_guilds(
4638
Ok(Json(json!(guilds)))
4739
}
4840

49-
async fn verify_user_admin(
50-
guild_id: &Id<GuildMarker>,
51-
discord_user: &Arc<twilight_http::Client>,
52-
app_state: &AppState,
53-
) -> Result<(), StatusCode> {
54-
let logged_in_user = discord_user
55-
.current_user()
56-
.await
57-
.unwrap()
58-
.model()
59-
.await
60-
.unwrap();
61-
62-
let guild = match app_state.discord_bot.guild(*guild_id).await {
63-
Ok(g) => g.model().await.unwrap(),
64-
Err(e) => {
65-
warn!("Error fetching guild: {:?}", e);
66-
return Err(StatusCode::NOT_FOUND);
67-
}
68-
};
69-
70-
let user_is_owner = guild.owner_id == logged_in_user.id;
71-
72-
if !user_is_owner {
73-
let member = match app_state
74-
.discord_bot
75-
.guild_member(*guild_id, logged_in_user.id)
76-
.await
77-
{
78-
Ok(gm) => gm.model().await.unwrap(),
79-
Err(e) => {
80-
warn!("Error fetching guild member: {:?}", e);
81-
return Err(StatusCode::NOT_FOUND);
82-
}
83-
};
84-
85-
let user_has_admin = guild.roles.iter().any(|r| {
86-
(r.permissions & Permissions::ADMINISTRATOR) == Permissions::ADMINISTRATOR
87-
&& member.roles.contains(&r.id)
88-
});
89-
if !user_has_admin {
90-
warn!("User does not have admin permissions in guild {}", guild_id);
91-
return Err(StatusCode::NOT_FOUND);
92-
}
93-
}
94-
Ok(())
95-
}
96-
9741
async fn get_guilds_id(
9842
Path(guild_id): Path<Id<GuildMarker>>,
9943
Extension(discord_user): Extension<Arc<twilight_http::Client>>,
10044
State(app_state): State<AppState>,
10145
) -> Result<Json<Value>, StatusCode> {
102-
verify_user_admin(&guild_id, &discord_user, &app_state).await?;
46+
if !utils::is_intersect_admin_guild(guild_id, &discord_user, &app_state.discord_bot).await.map_err(utils::ise)? {
47+
warn!("User is not an admin in guild {}", guild_id);
48+
return Err(StatusCode::NOT_FOUND);
49+
}
10350

10451
let guild = Guild::from_db(&guild_id.to_string(), &app_state.dynamo).await;
10552

api/src/guilds/models.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,10 +108,10 @@ impl Guild {
108108
.send()
109109
.await
110110
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)
111-
.and_then(|resp| {
111+
.map(|resp| {
112112
let item = resp.item.unwrap_or_default();
113113
let guild: Guild = (&item).into();
114-
Ok(guild)
114+
guild
115115
}) {
116116
Ok(guild) => Some(guild),
117117
Err(e) => {

api/src/guilds/utils.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
use http::StatusCode;
2+
use lambda_http::tracing::{error, info};
3+
use twilight_http::Error;
4+
use twilight_model::guild::Permissions;
5+
use twilight_model::id::Id;
6+
use twilight_model::id::marker::GuildMarker;
7+
use twilight_model::user::CurrentUserGuild;
8+
9+
pub fn ise(e: Error) -> StatusCode {
10+
error!("Internal Server Error: {:?}", e);
11+
StatusCode::INTERNAL_SERVER_ERROR
12+
}
13+
14+
async fn client_admin_guilds(client: &twilight_http::Client) -> Vec<CurrentUserGuild> {
15+
client.current_user_guilds().await.unwrap().models().await.unwrap()
16+
.into_iter()
17+
.filter(|g| g.permissions & Permissions::ADMINISTRATOR == Permissions::ADMINISTRATOR)
18+
.collect()
19+
}
20+
21+
pub async fn intersect_admin_guilds(
22+
client_1: &twilight_http::Client,
23+
client_2: &twilight_http::Client
24+
) -> Vec<CurrentUserGuild> {
25+
let client_1_guilds = client_admin_guilds(client_1).await;
26+
let client_2_guilds = client_admin_guilds(client_2).await;
27+
28+
client_1_guilds
29+
.into_iter()
30+
.filter(|g| client_2_guilds.iter().any(|g2| g.id == g2.id))
31+
.collect()
32+
}
33+
34+
async fn is_client_admin_guild(guild_id: Id<GuildMarker>, client: &twilight_http::Client) -> Result<bool, Error> {
35+
let guilds = client.current_user_guilds().after(Id::new(guild_id.get()-1)).limit(1).await?.models().await.unwrap();
36+
let admin_guilds: Vec<CurrentUserGuild> = guilds
37+
.into_iter()
38+
.filter(|g| g.permissions & Permissions::ADMINISTRATOR == Permissions::ADMINISTRATOR)
39+
.collect();
40+
Ok(admin_guilds.len() == 1)
41+
}
42+
43+
pub async fn is_intersect_admin_guild(
44+
guild_id: Id<GuildMarker>,
45+
client_1: &twilight_http::Client,
46+
client_2: &twilight_http::Client
47+
) -> Result<bool, Error> {
48+
Ok(
49+
is_client_admin_guild(guild_id, client_1).await? && is_client_admin_guild(guild_id, client_2).await?)
50+
}

ui/src/components/dashboard/VerifyComponent.vue

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,15 @@
33
import {ref} from "vue";
44
import {getUser} from "../../stores/auth.js";
55
6+
defineProps(
7+
{
8+
guild: {
9+
type: Object,
10+
required: true
11+
}
12+
}
13+
)
14+
615
const userRef = ref(getUser())
716
817
function getVerifyRolesFromKB() {
@@ -38,7 +47,7 @@ function getVerifyRolesFromKB() {
3847
</tr>
3948
</thead>
4049
<tbody>
41-
<tr v-for="role in getVerifyRolesFromKB()">
50+
<tr v-for="role in $props.guild.verify.roles">
4251
<td>
4352
{{ role.role_name }}
4453
</td>

ui/src/helpers/discordapi.js

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,17 @@ import {getUser} from "../stores/auth.js";
44
let user = getUser();
55

66
export async function getUserAdminGuilds() {
7-
let guilds = (await axios.get('https://discord.com/api/v10/users/@me/guilds', {
7+
return (await axios.get('https://discord.com/api/v10/users/@me/guilds', {
88
headers: {
99
'Authorization': 'Bearer ' + user.token.accessToken
1010
}
11-
})).data
12-
//convert to map of id to obj
13-
return guilds.filter(g => (Number(g.permissions) & (1 << 3)) === (1 << 3))
14-
.reduce((acc, g) => {
15-
acc[g.id] = g;
16-
return acc;
17-
}, {});
11+
})).data.filter(guild => guild.permissions && (BigInt(guild.permissions) & BigInt(0x0000000000000008)) === BigInt(0x0000000000000008));
12+
}
13+
14+
export async function getUserAdminGuildsAsMap() {
15+
let guilds = await getUserAdminGuilds();
16+
return Object.values(guilds).reduce((acc, guild) => {
17+
acc.set(guild.id, guild);
18+
return acc;
19+
}, new Map());
1820
}

ui/src/helpers/kbguild.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@ export async function getGuilds() {
1414
return resp.data;
1515
}
1616

17+
export async function getGuildsAsMap() {
18+
let guilds = await getGuilds();
19+
return Object.values(guilds).reduce((acc, guild) => {
20+
acc.set(guild.guild_id, guild);
21+
return acc;
22+
}, new Map());
23+
}
24+
1725
export async function getGuild(guild_id) {
1826
return (await axios.get(`${VITE_KB_API_URL}/guilds/${guild_id}`, {
1927
headers: {

ui/src/pages/dashboard/DashBaseView.vue

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,41 +3,36 @@
33
import KoalaMonoIcon from "../../components/icons/KoalaMonoIcon.vue";
44
import {onMounted, ref} from "vue";
55
import DashBody from "./body/DashBody.vue";
6-
import ThemeToggle from "../../components/ThemeToggle.vue";
76
import DiscordAuthButton from "../../components/auth/DiscordAuthButton.vue";
87
import MainWithFooter from "../../components/MainWithFooter.vue";
9-
import {getUserAdminGuilds} from "../../helpers/discordapi.js";
10-
import {getGuild, getGuilds} from "../../helpers/kbguild.js";
8+
import {getUserAdminGuildsAsMap} from "../../helpers/discordapi.js";
9+
import {getGuild, getGuildsAsMap} from "../../helpers/kbguild.js";
1110
import {INVITE_URL} from "../../helpers/redirect.js";
1211
13-
const HOME_PATH = "/dashboard"
14-
const VERIFY_PATH = "/dashboard/verify"
15-
const ANNOUNCE_PATH = "/dashboard/announce"
16-
1712
const currentPath = ref(window.location.pathname)
1813
1914
window.addEventListener('hashchange', () => {
2015
currentPath.value = window.location.pathname
2116
})
2217
23-
let guildsDsc = ref({})
24-
let guildsKb = ref({})
18+
let guildsDsc = ref(new Map())
19+
let guildsKb = ref(new Map())
2520
let currentGuildId = ref()
2621
2722
2823
onMounted(async () => {
29-
guildsDsc.value = await getUserAdminGuilds();
24+
guildsDsc.value = await getUserAdminGuildsAsMap();
3025
3126
// Load remaining guilds
3227
await sync_guilds_kb();
3328
console.log("Loaded guilds", guildsKb.value);
34-
await setCurrentGuild(Object.keys(guildsKb.value)[0]);
29+
console.log("Loaded guilds", guildsKb.value.values().next().value.guild_id);
3530
})
3631
3732
async function setCurrentGuild(gid) {
3833
currentGuildId.value = gid
3934
try {
40-
guildsKb.value[gid] = await getGuild(gid) // Refresh from db
35+
guildsKb.value.set(gid, await getGuild(gid)) // Refresh from db
4136
} catch (e) {
4237
if (e.response && e.response.status === 404) {
4338
// Allowed, means Koala not in server
@@ -48,7 +43,7 @@ async function setCurrentGuild(gid) {
4843
}
4944
5045
async function sync_guilds_kb() {
51-
guildsKb.value = await getGuilds()
46+
guildsKb.value = await getGuildsAsMap()
5247
}
5348
5449
</script>
@@ -60,17 +55,20 @@ async function sync_guilds_kb() {
6055
<div class="navbar shadow m-5 w-auto bg-base-200">
6156
<div class="navbar-start">
6257
<div class="dropdown">
58+
<div tabindex="0" role="button" class="btn btn-sm btn-primary" v-if="!currentGuildId">
59+
Select Guild
60+
</div>
6361
<div tabindex="0" role="button" class="card-title btn btn-sm btn-ghost" v-if="currentGuildId">
6462
<div class="avatar">
6563
<div class="w-6 rounded-xl">
66-
<img :src="`https://cdn.discordapp.com/icons/${currentGuildId}/${guildsDsc[currentGuildId].icon}.webp`" v-if="guildsDsc[currentGuildId].icon"/>
64+
<img :src="`https://cdn.discordapp.com/icons/${currentGuildId}/${guildsDsc.get(currentGuildId).icon}.webp`" v-if="guildsDsc.get(currentGuildId).icon"/>
6765
</div>
6866
</div>
69-
{{ guildsDsc[currentGuildId].name }}
67+
{{ guildsDsc.get(currentGuildId).name }}
7068
</div>
7169
<ul tabindex="0" class="dropdown-content menu bg-base-100 rounded-box z-1 p-2 shadow-sm">
72-
<li v-for="(gid, guild) in guildsKb" :class="(!guildsKb[guild.id] && 'menu-disabled')"><a :class="(gid === currentGuildId && 'menu-active')" @click="setCurrentGuild(gid)">
73-
<div class="w-6 rounded-xl"><img :src="`https://cdn.discordapp.com/icons/${gid}/${guildsDsc[gid].icon}.webp`" v-if="guildsDsc[gid] && guildsDsc[gid].icon"/>
70+
<li v-for="[gid, guild] in guildsDsc" :class="(!guildsKb.has(guild.id) && 'menu-disabled')"><a :class="(gid === currentGuildId && 'menu-active')" @click="setCurrentGuild(gid)">
71+
<div class="w-6 rounded-xl"><img :src="`https://cdn.discordapp.com/icons/${gid}/${guildsDsc.get(gid).icon}.webp`" v-if="guildsDsc.has(gid) && guildsDsc.get(gid).icon"/>
7472
</div> {{ guild.name }}</a></li>
7573
</ul>
7674
</div>
@@ -85,9 +83,9 @@ async function sync_guilds_kb() {
8583
</div>
8684
</div>
8785
</header>
88-
<DashBody v-if="currentPath === HOME_PATH && guildsKb[currentGuildId]"/>
86+
<DashBody v-if="guildsKb.has(currentGuildId)" :guild="guildsKb.get(currentGuildId)"/>
8987
<div class="flex flex-row justify-center">
90-
<div class="card card-sm m-5 p-10 shadow bg-base-200 flex w-fit" v-if="!guildsKb[currentGuildId]">
88+
<div class="card card-sm m-5 p-10 shadow bg-base-200 flex w-fit" v-if="!guildsKb.has(currentGuildId)">
9189
<div class="flex flex-row justify-center p-2">
9290
<h1 class="card-title">
9391
You need to invite KoalaBot to your server to use the dashboard silly!

ui/src/pages/dashboard/body/DashBody.vue

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,22 @@ import StreamAlertComponent from "../../../components/dashboard/StreamAlertCompo
66
import AnnounceComponent from "../../../components/dashboard/AnnounceComponent.vue";
77
import ColourRoleComponent from "../../../components/dashboard/ColourRoleComponent.vue";
88
import VoteComponent from "../../../components/dashboard/VoteComponent.vue";
9+
10+
defineProps(
11+
{
12+
guild: {
13+
type: Object,
14+
required: true
15+
}
16+
}
17+
)
918
</script>
1019

1120
<template>
1221

1322
<div class="lg:columns-2 sm:columns-1">
1423
<InsightsStatsComponent class="m-5 break-inside-avoid-column"/>
15-
<VerifyComponent class="m-5 break-inside-avoid-column"/>
24+
<VerifyComponent class="m-5 break-inside-avoid-column" :guild="$props.guild"/>
1625
<StreamAlertComponent class="m-5 break-inside-avoid-column"/>
1726
<AnnounceComponent class="m-5 break-inside-avoid-column"/>
1827
<ColourRoleComponent class="m-5 break-inside-avoid-column"/>

ui/src/router/RouterView.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ const routes = {
1818
'^/verify/google/callback$': VerifyGoogleCallback,
1919
'^/verify/email/callback$': VerifyEmailCallback,
2020
'^/verify/email/wait$': VerifyEmailWait,
21-
'^/dashboard(.*)$': DashBaseView,
21+
'^/dashboard$': DashBaseView,
2222
'^/404$': NotFoundView
2323
}
2424

0 commit comments

Comments
 (0)