Added Team System

This commit is contained in:
Xeovalyte 2023-05-09 13:57:01 +02:00
parent 10183ac56b
commit d2ab24ed64
37 changed files with 1733 additions and 665 deletions

View File

@ -0,0 +1,17 @@
const { Events } = require('discord.js');
module.exports = {
name: Events.MessageCreate,
async execute({ log }, message) {
if (message.channelId === process.env.MINECRAFT_CHANNEL_ID && !message.author.bot) {
await fetch(process.env.WEB_HOST + '/api/minecraft/message/chattominecraft', {
method: 'POST',
body: JSON.stringify({
content: message.content,
discordId: message.author.id,
}),
});
}
},
};

View File

@ -1,6 +1,6 @@
const { EmbedBuilder } = require('discord.js');
const registerEvents = ({ createEmbed, client }) => {
const registerEvents = ({ client }) => {
client.player.on('trackStart', (queue, track) => {

View File

@ -4,6 +4,7 @@ const { Player } = require('discord-player');
const fs = require('node:fs');
const path = require('node:path');
const dotenv = require('dotenv');
const express = require('express');
const createEmbed = require('./functions/createEmbed.js');
dotenv.config();
@ -15,11 +16,25 @@ const log = {
};
// Register client and music events
const client = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildVoiceStates, GatewayIntentBits.GuildMembers] });
const client = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildVoiceStates, GatewayIntentBits.GuildMembers, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent] });
client.player = new Player(client);
require('./functions/player.js').registerEvents({ client, createEmbed });
// Express
const app = express();
app.use(express.json());
const messageRoute = require('./routes/Message');
app.use('/minecraft', messageRoute);
app.listen('4000', () => {
log.Info('Express app running');
});
// Command handling
client.commands = new Collection();
@ -53,3 +68,5 @@ for (const file of eventFiles) {
}
client.login(process.env.DISCORD_TOKEN);
module.exports.client = client;

File diff suppressed because it is too large Load Diff

View File

@ -17,6 +17,8 @@
"discord.js": "^14.7.1",
"distube": "^4.0.4",
"dotenv": "^16.0.3",
"eslint": "^8.29.0"
"eslint": "^8.29.0",
"express": "^4.18.2",
"ytdl-core": "^4.11.4"
}
}

View File

@ -0,0 +1,38 @@
const { WebhookClient, EmbedBuilder } = require('discord.js');
const express = require('express');
const router = express.Router();
const webhookClient = new WebhookClient({ url: process.env.MINECRAFT_WEBHOOK_URL });
router.post('/sendchatmessage', (req, res) => {
const { content, username, avatarURL } = req.body;
if (!username || !content || !avatarURL) return res.status(400).send({ error: 'Content, username and avatar_url are required' });
webhookClient.send({
content: content,
username: username,
avatarURL: avatarURL,
});
res.send({ data: 'Test data' });
});
router.post('/sendgamemessage', (req, res) => {
const { content, avatarURL } = req.body;
if (!content || !avatarURL) return res.status(400).send({ error: 'Content, username and avatar_url are required' });
const messageEmbed = new EmbedBuilder()
.setColor(process.env.EMBED_COLOR)
.setAuthor({ name: content, iconURL: avatarURL });
webhookClient.send({
embeds: [messageEmbed],
username: 'Server',
});
res.send({ data: 'Test data' });
});
module.exports = router;

View File

@ -24,7 +24,6 @@ loom {
mods {
"polarcraft-mod" {
sourceSet sourceSets.main
sourceSet sourceSets.client
}
}

View File

@ -1,10 +0,0 @@
package com.xeovalyte.polarcraft;
import net.fabricmc.api.ClientModInitializer;
public class ExampleModClient implements ClientModInitializer {
@Override
public void onInitializeClient() {
// This entrypoint is suitable for setting up client-specific logic, such as rendering.
}
}

View File

@ -1,15 +0,0 @@
package com.xeovalyte.polarcraft.mixin.client;
import net.minecraft.client.MinecraftClient;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(MinecraftClient.class)
public class ExampleClientMixin {
@Inject(at = @At("HEAD"), method = "run")
private void run(CallbackInfo info) {
// This code is injected into the start of MinecraftClient.run()V
}
}

View File

@ -1,11 +0,0 @@
{
"required": true,
"package": "com.xeovalyte.polarcraft.mixin.client",
"compatibilityLevel": "JAVA_17",
"client": [
"ExampleClientMixin"
],
"injectors": {
"defaultRequire": 1
}
}

View File

@ -1,33 +1,23 @@
package com.xeovalyte.polarcraft;
import net.fabricmc.api.ModInitializer;
import com.xeovalyte.polarcraft.util.messageFunctions;
import com.xeovalyte.polarcraft.util.verifyFunction;
import net.fabricmc.api.ModInitializer;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.network.message.SignedMessage;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.io.File;
import com.google.gson.JsonObject;
import com.google.gson.JsonElement;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.util.UUID;
import net.fabricmc.fabric.api.message.v1.ServerMessageEvents;
public class PolarcraftMod implements ModInitializer {
@ -39,8 +29,9 @@ public class PolarcraftMod implements ModInitializer {
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
private static final File CONFIG_FILE = new File(FabricLoader.getInstance().getConfigDir().toFile(), "polarcraft.json");
private String configWebhookUrl = "https://discordapp.com/api/webhooks/webhookid/webhooktoken";
private String configVerifyUrl = "https://example.com/api/minecraft/verifyuuid";
public static String configChatMessageUrl = "https://example.com/api/minecraft/message/chattodiscord";
public static String configGameMessageUrl = "https://example.com/api/minecraft/message/gametodiscord";
public static String configVerifyUrl = "https://example.com/api/minecraft/verifyuuid";
@Override
public void onInitialize() {
@ -51,15 +42,17 @@ public class PolarcraftMod implements ModInitializer {
ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> {
ServerPlayerEntity player = (ServerPlayerEntity) handler.player;
onPlayerJoin(player);
verifyFunction.onPlayerJoin(player);
});
ServerPlayConnectionEvents.DISCONNECT.register((handler, server) -> {
ServerPlayerEntity player = (ServerPlayerEntity) handler.player;
messageFunctions.sendGameMessage(player.getUuid(), player.getDisplayName().getString() + " left the game");
});
ServerMessageEvents.CHAT_MESSAGE.register((message, sender, params) -> {
onChatMessage(message, sender);
});
ServerMessageEvents.GAME_MESSAGE.register((server, message, overlay) -> {
onGameMessage(server, message);
messageFunctions.sendChatMessage(message, sender);
});
}
@ -71,7 +64,8 @@ public class PolarcraftMod implements ModInitializer {
try {
MyModConfig config = GSON.fromJson(FileUtils.readFileToString(CONFIG_FILE, StandardCharsets.UTF_8), MyModConfig.class);
configWebhookUrl = config.webhookUrl;
configChatMessageUrl = config.chatMessageUrl;
configGameMessageUrl = config.gameMessageUrl;
configVerifyUrl = config.verifyUrl;
} catch (IOException e) {
e.printStackTrace();
@ -80,120 +74,21 @@ public class PolarcraftMod implements ModInitializer {
private void saveConfig() {
try {
FileUtils.writeStringToFile(CONFIG_FILE, GSON.toJson(new MyModConfig(configWebhookUrl, configVerifyUrl)), StandardCharsets.UTF_8);
FileUtils.writeStringToFile(CONFIG_FILE, GSON.toJson(new MyModConfig(configChatMessageUrl, configGameMessageUrl,configVerifyUrl)), StandardCharsets.UTF_8);
} catch (IOException e) {
e.printStackTrace();
}
}
public static class MyModConfig {
public String webhookUrl;
public String chatMessageUrl;
public String gameMessageUrl;
public String verifyUrl;
public MyModConfig(String webhookUrl, String verifyUrl) {
this.webhookUrl = webhookUrl;
public MyModConfig(String chatMessageUrl, String gameMessageUrl,String verifyUrl) {
this.chatMessageUrl = chatMessageUrl;
this.gameMessageUrl = gameMessageUrl;
this.verifyUrl = verifyUrl;
}
}
private void onChatMessage( SignedMessage message, ServerPlayerEntity sender) {
try {
// Create a URL object for the server endpoint
URL url = new URL(configWebhookUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/json");
connection.setDoOutput(true);
String requestBody = "{\"content\":\"" + sender.getName().getString() + " > "+ message.getSignedContent() + "\"}";
OutputStream outputStream = connection.getOutputStream();
outputStream.write(requestBody.getBytes());
outputStream.flush();
outputStream.close();
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line;
StringBuilder response = new StringBuilder();
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
private void onGameMessage( MinecraftServer server, Text message) {
try {
// Create a URL object for the server endpoint
URL url = new URL(configWebhookUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/json");
connection.setDoOutput(true);
String requestBody = "{\"content\":\"" + "**" + message.getString() + "**" + "\"}";
OutputStream outputStream = connection.getOutputStream();
outputStream.write(requestBody.getBytes());
outputStream.flush();
outputStream.close();
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line;
StringBuilder response = new StringBuilder();
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
private void onPlayerJoin(ServerPlayerEntity player) {
UUID uuid = player.getUuid();
try {
URL url = new URL(configVerifyUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/json");
connection.setDoOutput(true);
String requestBody = "{\"uuid\":\"" + uuid + "\"}";
OutputStream outputStream = connection.getOutputStream();
outputStream.write(requestBody.getBytes());
outputStream.flush();
outputStream.close();
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line;
StringBuilder response = new StringBuilder();
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
Gson gson = new Gson();
JsonElement element = gson.fromJson(response.toString(), JsonElement.class);
JsonObject jsonResponse = element.getAsJsonObject();
boolean verified = jsonResponse.get("verified").getAsBoolean();
if (!verified) {
int code = jsonResponse.get("code").getAsInt();
player.networkHandler.disconnect(Text.literal("Whitelist yourself by using this code: " + code));
}
} catch (IOException e) {
player.networkHandler.disconnect(Text.literal("There was an error while verifing your account"));
e.printStackTrace();
}
}
}

View File

@ -1,15 +0,0 @@
package com.xeovalyte.polarcraft.mixin;
import net.minecraft.server.MinecraftServer;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(MinecraftServer.class)
public class ExampleMixin {
@Inject(at = @At("HEAD"), method = "loadWorld")
private void init(CallbackInfo info) {
// This code is injected into the start of MinecraftServer.loadWorld()V
}
}

View File

@ -0,0 +1,31 @@
package com.xeovalyte.polarcraft.mixin;
import com.xeovalyte.polarcraft.util.messageFunctions;
import net.minecraft.advancement.Advancement;
import net.minecraft.advancement.PlayerAdvancementTracker;
import net.minecraft.server.network.ServerPlayerEntity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.Objects;
@Mixin(PlayerAdvancementTracker.class)
public class MixinPlayerAdvancementTracker {
@Shadow
private ServerPlayerEntity owner;
@Inject(method = "grantCriterion",
at = @At(
target = "Lnet/minecraft/server/PlayerManager;broadcast(Lnet/minecraft/text/Text;Z)V",
value = "INVOKE"
)
)
private void grantCriterion(Advancement advancement, String criterionName, CallbackInfoReturnable<Boolean> cir) {
// messageFunctions.sendGameMessage(owner.getUuid(), " has completed the challenge [" + Objects.requireNonNull(advancement.getDisplay()).getTitle().getString() + "]");
messageFunctions.sendGameMessage(owner.getUuid(), owner.getDisplayName().getString() + " has completed the advancement [" + Objects.requireNonNull(advancement.getDisplay()).getTitle().getString() + "]");
}
}

View File

@ -0,0 +1,27 @@
package com.xeovalyte.polarcraft.mixin;
import com.xeovalyte.polarcraft.util.messageFunctions;
import net.minecraft.entity.damage.DamageSource;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
@Mixin(ServerPlayerEntity.class)
public class MixinPlayerEntity {
@Inject(method = "onDeath",
at = @At(
target = "Lnet/minecraft/server/network/ServerPlayNetworkHandler;sendPacket(Lnet/minecraft/network/packet/Packet;Lnet/minecraft/network/PacketCallbacks;)V",
value = "INVOKE",
ordinal = 0),
locals = LocalCapture.CAPTURE_FAILSOFT
)
private void onDeath(DamageSource damageSource, CallbackInfo ci, boolean bl, Text text){
ServerPlayerEntity serverPlayerEntity = (ServerPlayerEntity) (Object) this;
messageFunctions.sendGameMessage(serverPlayerEntity.getUuid(), text.getString());
}
}

View File

@ -0,0 +1,74 @@
package com.xeovalyte.polarcraft.util;
import com.xeovalyte.polarcraft.PolarcraftMod;
import net.minecraft.network.message.SignedMessage;
import net.minecraft.server.network.ServerPlayerEntity;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.UUID;
public class messageFunctions {
public static void sendGameMessage(UUID uuid, String message) {
try {
// Create a URL object for the server endpoint
URL url = new URL(PolarcraftMod.configGameMessageUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/json");
connection.setDoOutput(true);
String requestBody = "{\"content\":\"" + message + "\", \"uuid\":\"" + uuid + "\"}";
OutputStream outputStream = connection.getOutputStream();
outputStream.write(requestBody.getBytes());
outputStream.flush();
outputStream.close();
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line;
StringBuilder response = new StringBuilder();
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void sendChatMessage( SignedMessage message, ServerPlayerEntity sender) {
try {
// Create a URL object for the server endpoint
URL url = new URL(PolarcraftMod.configChatMessageUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/json");
connection.setDoOutput(true);
String requestBody = "{\"content\":\"" + message.getSignedContent() + "\", \"uuid\":\"" + sender.getUuid() + "\"}";
OutputStream outputStream = connection.getOutputStream();
outputStream.write(requestBody.getBytes());
outputStream.flush();
outputStream.close();
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line;
StringBuilder response = new StringBuilder();
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

View File

@ -0,0 +1,66 @@
package com.xeovalyte.polarcraft.util;
import com.xeovalyte.polarcraft.PolarcraftMod;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.UUID;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;
public class verifyFunction {
public static void onPlayerJoin(ServerPlayerEntity player) {
UUID uuid = player.getUuid();
try {
URL url = new URL(PolarcraftMod.configVerifyUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/json");
connection.setDoOutput(true);
String requestBody = "{\"uuid\":\"" + uuid + "\"}";
OutputStream outputStream = connection.getOutputStream();
outputStream.write(requestBody.getBytes());
outputStream.flush();
outputStream.close();
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line;
StringBuilder response = new StringBuilder();
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
Gson gson = new Gson();
JsonElement element = gson.fromJson(response.toString(), JsonElement.class);
JsonObject jsonResponse = element.getAsJsonObject();
boolean verified = jsonResponse.get("verified").getAsBoolean();
if (!verified) {
int code = jsonResponse.get("code").getAsInt();
player.networkHandler.disconnect(Text.literal("Whitelist yourself by using this code: " + code));
} else {
messageFunctions.sendGameMessage(player.getUuid(), player.getDisplayName().getString() + " joined the game");
}
} catch (IOException e) {
player.networkHandler.disconnect(Text.literal("There was an error while verifing your account"));
e.printStackTrace();
}
}
}

View File

@ -13,18 +13,14 @@
},
"license": "CC0-1.0",
"icon": "assets/polarcraft-mod/icon.png",
"environment": "*",
"environment": "server",
"entrypoints": {
"main": [
"com.xeovalyte.polarcraft.PolarcraftMod"
]
},
"mixins": [
"polarcraft-mod.mixins.json",
{
"config": "polarcraft-mod.client.mixins.json",
"environment": "client"
}
"polarcraft-mod.mixins.json"
],
"depends": {
"fabricloader": ">=0.14.19",

View File

@ -3,7 +3,8 @@
"package": "com.xeovalyte.polarcraft.mixin",
"compatibilityLevel": "JAVA_17",
"mixins": [
"ExampleMixin"
"MixinPlayerAdvancementTracker",
"MixinPlayerEntity"
],
"injectors": {
"defaultRequire": 1

View File

@ -0,0 +1,180 @@
<template>
<Modal v-if="modalOpen" title="Invite team member" @close="modalOpen = false" @submit="modalOpen = false">
<div>
<h2 class="text-lg font-bold text-primary">Users</h2>
<div class="h-48 space-y-3 overflow-y-auto">
<div v-for="unaffiliatedUser in unaffilatedUsers" :key="unaffiliatedUser._id.toString()" class="flex items-center rounded bg-neutral-700 px-3 py-1 text-gray-200">
{{ unaffiliatedUser.username }}
<Button v-if="!unaffiliatedUser.teamInvites.includes(user.team.id)" class="ml-auto" @click="inviteUser(unaffiliatedUser)">Invite</Button>
<Button v-else class="ml-auto" type="danger" @click="cancelInvite(unaffiliatedUser)">Cancel Invite</Button>
</div>
</div>
</div>
</Modal>
<Modal v-if="editTeamModal.open" title="Edit team" @close="editTeamModal.open = false" @submit="editTeam">
<Input v-model:value="editTeamModal.name" background-class="bg-neutral-800" class="w-full max-w-sm">Naam / Prefix</Input>
<Colorpicker v-model:value="editTeamModal.color" input-background-class="bg-neutral-800" class="w-full max-w-sm" />
</Modal>
<div class="mx-auto my-10 max-w-2xl">
<h2 class="mb-2 text-xl font-bold text-primary">Team Information</h2>
<div class="rounded border-[1px] border-primary px-5 py-2 text-primary">
<table class="w-full table-auto">
<tbody class="divide-y divide-gray-700">
<tr>
<td class="py-3">Name</td>
<td class="font-bold">{{ team.name }}</td>
</tr>
<tr>
<td class="py-3">Color</td>
<td class="font-bold">{{ team.color }}</td>
</tr>
<tr>
<td class="py-3">ID</td>
<td class="font-bold">{{ team._id }}</td>
</tr>
</tbody>
</table>
</div>
<div class="mb-10 mt-5 flex justify-center gap-x-3">
<Button type="danger" @click="leaveTeam">Leave Team</Button>
<Button @click="openTeamModal">Edit Team</Button>
</div>
<h2 class="mb-2 text-xl font-bold text-primary">Team Members</h2>
<div class="space-y-5 rounded border-[1px] border-primary p-5 text-primary">
<div v-for="teamMember in teamMembers" :key="teamMember._id" class="flex h-12 items-center rounded bg-neutral-800 px-5 font-bold text-gray-200">
{{ teamMember.username }}
<span v-if="teamMember.team.admin" class="ml-3 text-sm text-gray-400">Admin</span>
<div v-if="user.team.admin" class="ml-auto">
<Button v-if="!teamMember.team.admin && teamMember._id !== user._id" @click="promoteUser(teamMember)">Promote</Button>
<Button v-if="teamMember.team.admin && teamMember._id !== user._id" @click="demoteUser(teamMember)">Demote</Button>
</div>
</div>
<div v-if="user.team.admin" class="rounded border-2 border-dashed border-neutral-500 bg-neutral-800 p-3 text-center font-bold text-gray-200 hover:cursor-pointer hover:bg-neutral-700" @click="modalOpen = true">
Invite new member
</div>
</div>
</div>
</template>
<script setup>
const user = useState('user');
const { data: team } = await useFetch('/api/team');
const { data: teamMembers } = await useFetch('/api/team/members');
const { data: unaffilatedUsers } = await useFetch('/api/team/unaffiliated')
const modalOpen = ref(false)
const editTeamModal = ref({
open: false,
name: '',
color: '',
})
const openTeamModal = () => {
editTeamModal.value.name = team.value.name
editTeamModal.value.color = team.value.color
editTeamModal.value.open = true
}
const leaveTeam = async () => {
try {
await $fetch('/api/team/leave');
user.value.team = null;
useToast().success('Successfully left team')
} catch (e) {
console.log(e);
useToast().error(e.statusMessage)
}
}
const editTeam = async () => {
try {
await $fetch('/api/team/edit', {
method: 'POST',
body: {
name: editTeamModal.value.name,
color: editTeamModal.value.color,
}
});
team.value.name = editTeamModal.value.name
team.value.color = editTeamModal.value.color
useToast().success('Successfully modified team')
editTeamModal.value.open = false
} catch (e) {
console.log(e);
useToast().error(e.statusMessage)
}
}
const inviteUser = async (usr) => {
try {
await $fetch('/api/team/invite', {
method: 'POST',
body: { id: usr._id }
});
usr.teamInvites.push(user.value.team.id);
useToast().success(`Successfully invited ${usr.username}`)
} catch (e) {
console.log(e);
useToast().error(e.statusMessage)
}
}
const cancelInvite = async (usr) => {
try {
await $fetch('/api/team/cancelinvite', {
method: 'POST',
body: { id: usr._id }
});
usr.teamInvites = usr.teamInvites.filter(a => a !== user.value.team.id);
useToast().success('Successfully cancelled invited')
} catch (e) {
console.log(e);
useToast().error(e.statusMessage)
}
}
const promoteUser = async (usr) => {
try {
await $fetch('/api/team/promote', {
method: 'POST',
body: { userId: usr._id }
});
usr.team.admin = true
console.log(usr)
console.log(teamMembers)
useToast().success('Successfully promted user')
} catch (e) {
console.log(e);
useToast().error(e.statusMessage)
}
}
const demoteUser = async (usr) => {
try {
await $fetch('/api/team/demote', {
method: 'POST',
body: { userId: usr._id }
});
usr.team.admin = false
useToast().success('Successfully demoted user')
} catch (e) {
console.log(e);
useToast().error(e.statusMessage)
}
}
</script>

View File

@ -4,28 +4,83 @@
</p>
<div class="mb-5 flex w-full justify-center gap-10">
<span
class="font-bold text-primary hover:cursor-pointer" :class="{ 'underline underline-offset-4': !createTeam }"
@click="createTeam = false"
class="font-bold text-primary hover:cursor-pointer" :class="{ 'underline underline-offset-4': !createTeam.show }"
@click="createTeam.show = false"
>
Team Invites
</span>
<span
class="font-bold text-primary hover:cursor-pointer" :class="{ 'underline underline-offset-4': createTeam }"
@click="createTeam = true"
class="font-bold text-primary hover:cursor-pointer" :class="{ 'underline underline-offset-4': createTeam.show }"
@click="createTeam.show = true"
>
Create Team
</span>
</div>
<div v-if="!createTeam" class="text-center text-gray-300">
You don't have any team invites
<div v-if="!createTeam.show" class="text-center text-gray-300">
<h2 v-if="!user.teamInvites.length">You don't have any team invites</h2>
<div v-else class="mx-auto flex max-w-lg flex-col">
<div v-for="team in filteredTeams" :key="team._id" class="flex items-center rounded bg-neutral-800 px-3 py-1 text-left" @click="acceptInvite(team)">
<span>
{{ team.name }}
</span>
<Button class="ml-auto">Accept</Button>
</div>
</div>
</div>
<div v-else class="flex w-full flex-col items-center gap-5">
<Input class="w-full max-w-sm">Naam / Prefix</Input>
<Colorpicker class="w-full max-w-sm" />
<Button>Create Team</Button>
<Input v-model:value="createTeam.name" class="w-full max-w-sm">Naam / Prefix</Input>
<Colorpicker v-model:value="createTeam.color" class="w-full max-w-sm" />
<Button @click="handleCreateTeam">Create Team</Button>
</div>
</template>
<script setup>
const createTeam = ref(false)
const user = useState('user')
const teams = await $fetch('/api/team/all')
const filteredTeams = computed(() => {
return teams.filter(a => user.value.teamInvites.includes(a._id));
})
const createTeam = ref({
show: false,
name: '',
color: '',
})
const acceptInvite = async (team) => {
try {
const response = await $fetch('/api/team/acceptinvite', {
method: 'POST',
body: { teamId: team._id }
})
console.log(response)
user.value.team = { id: response._id, admin: false }
useToast().success('Successfully joined team')
} catch (e) {
console.log(e);
useToast().error(e.statusMessage)
}
}
const handleCreateTeam = async () => {
try {
const response = await $fetch('/api/team/create', {
method: 'POST',
body: { teamName: createTeam.value.name, teamColor: createTeam.value.color }
})
user.value.team = { id: response.insertedId.toString(), admin: true }
useToast().success('Successfully created team')
} catch (e) {
console.log(e);
useToast().error(e.statusMessage)
}
}
</script>

View File

@ -1,13 +1,13 @@
<template>
<div class="h-full bg-neutral-900">
<div class="hidden h-full grid-cols-desktoplayout grid-rows-desktoplayout sm:grid">
<div v-if="user" class="hidden h-full grid-cols-desktoplayout grid-rows-desktoplayout sm:grid">
<LayoutNavbar class="col-span-2" />
<LayoutSidebar v-if="user.minecraft.uuid" class="" />
<div class="overflow-y-auto px-10 pt-5" :class="{ 'col-span-2': !user.minecraft.uuid }">
<NuxtPage />
</div>
</div>
<div class="h-full sm:hidden">
<div v-if="user" class="h-full sm:hidden">
<div class="overflow-y-auto p-2">
<NuxtPage />
</div>

4
web/package-lock.json generated
View File

@ -3160,7 +3160,7 @@
}
},
"node_modules/@xeovalyte/nuxt-xvui": {
"resolved": "git+https://gitea.xeovalyte.dev/xeovalyte/nuxt-xvui.git#e5bb7302f0626b39aa7b3a73bd4109b03702a9c7",
"resolved": "git+https://gitea.xeovalyte.dev/xeovalyte/nuxt-xvui.git#53b253beae85b014c2556291aaa9f8f94fc765d5",
"hasInstallScript": true,
"dependencies": {
"@nuxt/eslint-config": "^0.1.1",
@ -15838,7 +15838,7 @@
}
},
"@xeovalyte/nuxt-xvui": {
"version": "git+https://gitea.xeovalyte.dev/xeovalyte/nuxt-xvui.git#e5bb7302f0626b39aa7b3a73bd4109b03702a9c7",
"version": "git+https://gitea.xeovalyte.dev/xeovalyte/nuxt-xvui.git#53b253beae85b014c2556291aaa9f8f94fc765d5",
"from": "@xeovalyte/nuxt-xvui@git+https://gitea.xeovalyte.dev/xeovalyte/nuxt-xvui.git",
"requires": {
"@nuxt/eslint-config": "^0.1.1",

View File

@ -3,6 +3,15 @@
<h1 class="text-2xl font-bold text-primary">
Team
</h1>
<TeamNone />
<TeamNone v-if="!user.team" />
<TeamDefault v-else />
</div>
</template>
<script setup>
const user = useState('user')
definePageMeta({
middleware: ["auth"]
})
</script>

View File

@ -37,7 +37,7 @@ export default defineEventHandler(async (event) => {
},
}
await coll.updateOne({ 'discord.id': userResult.id }, { $set: doc, $setOnInsert: { minecraft: { uuid: null, username: null }, team: null } }, { upsert: true })
await coll.updateOne({ 'discord.id': userResult.id }, { $set: doc, $setOnInsert: { minecraft: { uuid: null, username: null }, teamInvites: [] } }, { upsert: true })
const token = createToken(tokenResponseData.access_token, tokenResponseData.refresh_token, tokenResponseData.expires_in, userResult.id )

View File

@ -0,0 +1,18 @@
import { ObjectId } from 'mongodb'
export default defineEventHandler(async (event) => {
const { teamId } = await readBody(event);
const user = await getAuth(event)
const teamsColl = db.collection('teams')
const usersColl = db.collection('users')
const team = await teamsColl.findOneAndUpdate({ _id: new ObjectId(teamId) }, { $inc: { count: 1 } })
if (!team.value) return createError({ statusCode: 500, statusMessage: 'Could not find team'})
await usersColl.updateOne({ _id: new ObjectId(user._id) }, { $set: { 'team.id': teamId, 'team.admin': false }})
return team
});

View File

@ -0,0 +1,8 @@
export default defineEventHandler(async () => {
const teamsColl = db.collection('teams')
const cursor = teamsColl.find();
const teams = await cursor.toArray()
return teams
});

View File

@ -0,0 +1,16 @@
import { ObjectId } from 'mongodb'
export default defineEventHandler(async (event) => {
const { id } = await readBody(event);
const user = await getAuth(event)
const teamsColl = db.collection('teams')
const usersColl = db.collection('users')
const team = await teamsColl.findOne({ _id: new ObjectId(user.team.id) })
usersColl.updateOne({ _id: new ObjectId(id) }, { $pull: { teamInvites: team._id.toString() } })
return team
});

View File

@ -0,0 +1,21 @@
export default defineEventHandler(async (event) => {
const { teamName, teamColor } = await readBody(event);
if (!teamName || !teamColor) return createError({ statusCode: 400, statusMessage: 'teamName and teamColor are required' })
if (!isHexColor(teamColor)) return createError({ statusCode: 400, statusMessage: 'Team color is not a valid hex code' })
const user = await getAuth(event)
if (user.team) return createError({ statusCode: 400, statusMessage: 'User already is in a team' })
const teamsColl = db.collection('teams')
const usersColl = db.collection('users')
if (await teamsColl.findOne({ name: teamName })) return createError({ statusCode: 400, statusMessage: 'Team name already exists' })
const response = await teamsColl.insertOne({ name: teamName, color: teamColor, count: 1 })
await usersColl.findOneAndUpdate({ 'discord.id': user.discord.id }, { $set: { 'team.id': response.insertedId.toString(), 'team.admin': true } })
return response;
});

View File

@ -0,0 +1,14 @@
import { ObjectId } from 'mongodb'
export default defineEventHandler(async (event) => {
const { userId } = await readBody(event)
const user = await getAuth(event)
if (!user.team.admin) return createError({ statusCode: 403, statusMessage: 'Forbidden' })
const usersColl = db.collection('users')
await usersColl.findOneAndUpdate({ _id: new ObjectId(userId) }, { $set: { 'team.admin': false } });
return { status: 'Success' }
});

View File

@ -0,0 +1,19 @@
import { ObjectId } from 'mongodb'
export default defineEventHandler(async (event) => {
const { name, color } = await readBody(event);
if (!isHexColor(color)) return createError({ statusCode: 400, statusMessage: 'Team color is not a valid hex code' })
const user = await getAuth(event)
const teamsColl = db.collection('teams')
const team = await teamsColl.findOne({ _id: new ObjectId(user.team.id) });
if (team.name !== name && await teamsColl.findOne({ name: name })) return createError({ statusCode: 400, statusMessage: 'Team name already exists' })
await teamsColl.updateOne({ _id: new ObjectId(user.team.id) }, { $set: { name: name, color: color } })
return team
});

View File

@ -0,0 +1,10 @@
import { ObjectId } from 'mongodb'
export default defineEventHandler(async (event) => {
const user = await getAuth(event)
const teamsColl = db.collection('teams')
const team = await teamsColl.findOne({ _id: new ObjectId(user.team.id) })
return team
});

View File

@ -0,0 +1,20 @@
import { ObjectId } from 'mongodb'
export default defineEventHandler(async (event) => {
const { id } = await readBody(event);
const user = await getAuth(event)
const teamsColl = db.collection('teams')
const usersColl = db.collection('users')
const team = await teamsColl.findOne({ _id: new ObjectId(user.team.id) })
const invitedUser = await usersColl.findOne({ _id: new ObjectId(id)});
if (invitedUser.team) return createError({ statusCode: 400, statusMessage: 'User already is in a team' })
usersColl.updateOne({ _id: new ObjectId(id) }, { $push: { teamInvites: team._id.toString() } })
return team
});

View File

@ -0,0 +1,16 @@
import { ObjectId } from 'mongodb'
export default defineEventHandler(async (event) => {
const user = await getAuth(event)
const teamsColl = db.collection('teams')
const team = await teamsColl.findOneAndUpdate({ _id: new ObjectId(user.team.id) }, { $inc: { count: -1 }})
const usersColl = db.collection('users')
await usersColl.findOneAndUpdate({ _id: new ObjectId(user._id) }, { $unset: { team: "" } })
if (team.value.count <= 1) {
await teamsColl.deleteOne({ _id: new ObjectId(user.team.id )})
}
return team
});

View File

@ -0,0 +1,17 @@
export default defineEventHandler(async (event) => {
const user = await getAuth(event)
const usersColl = db.collection('users')
const cursor = usersColl.find({ 'team.id': user.team.id })
if((await usersColl.countDocuments({ 'team.id': user.team.id })) === 0) {
return createError({ statusCode: 500, statusMessage: 'No users were found' })
}
const users = [];
for await (const doc of cursor) {
users.push(doc)
}
return users;
});

View File

@ -0,0 +1,14 @@
import { ObjectId } from 'mongodb'
export default defineEventHandler(async (event) => {
const { userId } = await readBody(event)
const user = await getAuth(event)
if (!user.team.admin) return createError({ statusCode: 403, statusMessage: 'Forbidden' })
const usersColl = db.collection('users')
await usersColl.findOneAndUpdate({ _id: new ObjectId(userId) },{ $set: { 'team.admin': true } });
return { status: 'Success' }
});

View File

@ -0,0 +1,7 @@
export default defineEventHandler(async (event) => {
const usersColl = db.collection('users')
const cursor = usersColl.find({ team: { $exists: false } })
const unaffiliatedUsers = await cursor.toArray()
return unaffiliatedUsers
});

View File

@ -0,0 +1,4 @@
export const isHexColor = (str) => {
const pattern = /^#([0-9A-F]{3}){1,2}$/i;
return pattern.test(str);
}