Lots of improvements and changes
Some checks failed
Build and Deploy / Deploy Web (push) Has been cancelled
Build and Deploy / Deploy Discord Bot (push) Has been cancelled

This commit is contained in:
Xeovalyte 2023-05-27 12:02:33 +02:00
parent 98d73ea593
commit 8121b9b975
21 changed files with 3147 additions and 1599 deletions

View File

@ -36,7 +36,7 @@ module.exports = {
});
await ticketChannel.send({ embeds: [createEmbed.basic(`${interaction.user} created a ticket`)]});
await ticketChannel.send({ embeds: [createEmbed.basic(`${interaction.user} created a ticket`)] });
interaction.editReply({ embeds: [createEmbed.basic(`${ticketChannel} has been created`)], emphemeral: true });
@ -52,14 +52,14 @@ module.exports = {
.setStyle(ButtonStyle.Danger),
);
const closeMessage = await interaction.reply({ embeds: [createEmbed.basic(`Closing ticket in 10 seconds...`)], components: [cancelRow]});
const closeMessage = await interaction.reply({ embeds: [createEmbed.basic('Closing ticket in 10 seconds...')], components: [cancelRow] });
const collector = closeMessage.createMessageComponentCollector();
const timeout = setTimeout(() => {
const timeout = setTimeout(() => {
try {
interaction.channel.delete()
interaction.channel.delete();
} catch {}
}, 10000)
}, 10000);
collector.on('collect', async () => {
clearTimeout(timeout);

View File

@ -5,7 +5,8 @@
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"lint": "eslint .",
"lint-fix": "eslint . --fix"
},
"author": "",
"license": "ISC",

View File

@ -7,14 +7,14 @@ const router = express.Router();
router.post('/createchannels', async (req, res) => {
const { name, discordId } = req.body;
if (!name || !discordId ) return res.status(400).send({ error: 'Name en discordId zijn vereist' });
if (!name || !discordId) return res.status(400).send({ error: 'Name en discordId zijn vereist' });
try {
const guild = await index.client.guilds.fetch(process.env.GUILD_ID);
const category = await guild.channels.fetch(process.env.TEAM_CATEGORY_ID);
const member = await guild.members.fetch(discordId)
const member = await guild.members.fetch(discordId);
const textChannel = await guild.channels.create({
name: name,
@ -27,9 +27,9 @@ router.post('/createchannels', async (req, res) => {
},
{
id: member.id,
allow: [PermissionsBitField.Flags.ViewChannel]
}
]
allow: [PermissionsBitField.Flags.ViewChannel],
},
],
});
const voiceChannel = await guild.channels.create({
@ -43,15 +43,15 @@ router.post('/createchannels', async (req, res) => {
},
{
id: member.id,
allow: [PermissionsBitField.Flags.ViewChannel]
}
]
allow: [PermissionsBitField.Flags.ViewChannel],
},
],
});
res.send({ textChannel, voiceChannel });
} catch (e) {
console.log(e);
return res.status(500).send({ error: 'Error tijdens het maken van discord channels' })
return res.status(500).send({ error: 'Error tijdens het maken van discord channels' });
}
});
@ -59,86 +59,86 @@ router.post('/createchannels', async (req, res) => {
router.post('/deletechannels', async (req, res) => {
const { textChannelId, voiceChannelId } = req.body;
if (!textChannelId, !voiceChannelId ) return res.status(400).send({ error: 'textChannelId en voiceChannelId zijn vereist' });
if (!textChannelId, !voiceChannelId) return res.status(400).send({ error: 'textChannelId en voiceChannelId zijn vereist' });
try {
const guild = await index.client.guilds.fetch(process.env.GUILD_ID);
const textChannel = await guild.channels.fetch(textChannelId);
const voiceChannel = await guild.channels.fetch(voiceChannelId);
await textChannel.delete()
await voiceChannel.delete()
await textChannel.delete();
await voiceChannel.delete();
res.send({ status: 'success' });
} catch (e) {
console.log(e);
return res.status(500).send({ error: 'Error tijdens het verwijderen van discord channels' })
return res.status(500).send({ error: 'Error tijdens het verwijderen van discord channels' });
}
});
router.post('/removeteammember', async (req, res) => {
const { textChannelId, voiceChannelId, discordId } = req.body;
if (!textChannelId, !voiceChannelId, !discordId ) return res.status(400).send({ error: 'textChannelId, voiceChannelId en discordId zijn vereist' });
if (!textChannelId, !voiceChannelId, !discordId) return res.status(400).send({ error: 'textChannelId, voiceChannelId en discordId zijn vereist' });
try {
const guild = await index.client.guilds.fetch(process.env.GUILD_ID);
const member = await guild.members.fetch(discordId)
const member = await guild.members.fetch(discordId);
const textChannel = await guild.channels.fetch(textChannelId);
const voiceChannel = await guild.channels.fetch(voiceChannelId);
await textChannel.permissionOverwrites.delete(member)
await voiceChannel.permissionOverwrites.delete(member)
await textChannel.permissionOverwrites.delete(member);
await voiceChannel.permissionOverwrites.delete(member);
res.send({ status: 'success' });
} catch (e) {
console.log(e);
return res.status(500).send({ error: 'Error tijdens het verwijderen van een team member' })
return res.status(500).send({ error: 'Error tijdens het verwijderen van een team member' });
}
});
router.post('/addteammember', async (req, res) => {
const { textChannelId, voiceChannelId, discordId } = req.body;
if (!textChannelId, !voiceChannelId, !discordId ) return res.status(400).send({ error: 'textChannelId, voiceChannelId en discordId zijn vereist' });
if (!textChannelId, !voiceChannelId, !discordId) return res.status(400).send({ error: 'textChannelId, voiceChannelId en discordId zijn vereist' });
try {
const guild = await index.client.guilds.fetch(process.env.GUILD_ID);
const member = await guild.members.fetch(discordId)
const member = await guild.members.fetch(discordId);
const textChannel = await guild.channels.fetch(textChannelId);
const voiceChannel = await guild.channels.fetch(voiceChannelId);
await textChannel.permissionOverwrites.edit(member, { ViewChannel: true })
await voiceChannel.permissionOverwrites.edit(member, { ViewChannel: true })
await textChannel.permissionOverwrites.edit(member, { ViewChannel: true });
await voiceChannel.permissionOverwrites.edit(member, { ViewChannel: true });
res.send({ status: 'success' });
} catch (e) {
console.log(e);
return res.status(500).send({ error: 'Error tijdens het toevoegen van team member' })
return res.status(500).send({ error: 'Error tijdens het toevoegen van team member' });
}
});
router.post('/edit', async (req, res) => {
const { textChannelId, voiceChannelId, name } = req.body;
if (!textChannelId, !voiceChannelId, !name ) return res.status(400).send({ error: 'textChannelId, voiceChannelId en name zijn vereist' });
if (!textChannelId, !voiceChannelId, !name) return res.status(400).send({ error: 'textChannelId, voiceChannelId en name zijn vereist' });
try {
const guild = await index.client.guilds.fetch(process.env.GUILD_ID);
const textChannel = await guild.channels.fetch(textChannelId);
const voiceChannel = await guild.channels.fetch(voiceChannelId);
await textChannel.edit({ name: name })
await voiceChannel.edit({ name: name })
await textChannel.edit({ name: name });
await voiceChannel.edit({ name: name });
res.send({ status: 'success' });
} catch (e) {
console.log(e);
return res.status(500).send({ error: 'Error tijds het veranderen van Discord channel naam' })
return res.status(500).send({ error: 'Error tijds het veranderen van Discord channel naam' });
}
});

View File

@ -1,27 +1,47 @@
const express = require('express');
const index = require('../index')
const index = require('../index');
const router = express.Router();
router.post('/changenickname', async (req, res) => {
const { nickname, discordId } = req.body;
if (!nickname || !discordId ) return res.status(400).send({ error: 'Nickname en discordId zijn vereist' });
if (!nickname || !discordId) return res.status(400).send({ error: 'Nickname en discordId zijn vereist' });
const nick = nickname.length > 32 ? nickname.slice(0, 32) : nickname;
const nick = nickname.length > 32 ? nickname.slice(0, 32) : nickname
try {
const guild = await index.client.guilds.fetch(process.env.GUILD_ID);
const member = await guild.members.fetch(discordId)
await member.edit({ nick: nick })
const member = await guild.members.fetch(discordId);
await member.edit({ nick: nick });
} catch (e) {
console.log(e);
return res.status(500).send({ error: 'Error tijds het veranderen van de nickname' })
return res.status(500).send({ error: 'Error tijds het veranderen van de nickname' });
}
res.send({ status: 'success' });
});
router.post('/ban', async (req, res) => {
const { discordId, reason } = req.body;
if (!reason || !discordId) return res.status(400).send({ error: 'Reason en discordId zijn vereist' });
try {
const guild = await index.client.guilds.fetch(process.env.GUILD_ID);
const member = await guild.members.fetch(discordId);
await member.ban({ deleteMessageSeconds: 24 * 3600, reason: reason });
} catch (e) {
console.log(e);
return res.status(500).send({ error: 'Error tijds het bannen van gebruiker' });
}
res.send({ status: 'success' });
});
module.exports = router;

View File

@ -7,7 +7,7 @@
te zien, vul deze code hieronder in.
</p>
<div class="flex gap-2">
<Input v-model:value="code">Code</Input>
<Input v-model="code">Code</Input>
<Button @click="submitCode">Submit</Button>
</div>
</div>

View File

@ -16,12 +16,16 @@
<Icon size="1.5em" name="ph:map-trifold" class="mr-3" />
Map
</NuxtLink>
<div v-if="user && user.admin" class="mt-auto">
<h2 class="ml-2 text-gray-400">Adminstration</h2>
<NuxtLink to="/admin/users" class="sidebar-item">
<div v-if="user && user.role.admin" class="mt-auto space-y-1">
<h2 class="ml-2 text-gray-400">Moderation</h2>
<NuxtLink to="/mod/users" class="sidebar-item">
<Icon size="1.5em" name="ph:users-three" class="mr-3" />
Users
</NuxtLink>
<NuxtLink to="/mod/server" class="sidebar-item">
<Icon size="1.5em" name="ph:cpu" class="mr-3" />
Server Control
</NuxtLink>
</div>
</div>
</template>

View File

@ -12,8 +12,8 @@
</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" />
<Input v-model="editTeamModal.name" background-class="bg-neutral-800" class="w-full max-w-sm">Naam / Prefix</Input>
<Colorpicker v-model="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>

View File

@ -28,8 +28,8 @@
</div>
</div>
<div v-else class="flex w-full flex-col items-center gap-5">
<Input v-model:value="createTeam.name" class="w-full max-w-sm">Naam</Input>
<Colorpicker v-model:value="createTeam.color" class="w-full max-w-sm" />
<Input v-model="createTeam.name" class="w-full max-w-sm">Naam</Input>
<Colorpicker v-model="createTeam.color" class="w-full max-w-sm" />
<Button @click="handleCreateTeam">Create Team</Button>
</div>
</template>

View File

@ -5,7 +5,7 @@ export default defineNuxtRouteMiddleware(async (to) => {
const user = await $fetch('/api/auth/user')
useState('user', () => user)
if (to.meta.admin && !user.admin) return navigateTo('/')
if (to.meta.moderator && !user.role.moderator) return navigateTo('/')
} catch (err) {
console.log(err)

4249
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -11,18 +11,18 @@
"lint-fix": "eslint . --fix"
},
"devDependencies": {
"@nuxt/devtools": "^0.4.2",
"@nuxt/devtools": "^0.5.0",
"@nuxt/image-edge": "^1.0.0-28059208.2abef1b",
"@nuxtjs/eslint-module": "^4.0.2",
"@nuxtjs/tailwindcss": "^6.6.6",
"@types/node": "^18",
"@vueuse/components": "^10.1.0",
"@vueuse/core": "^10.1.0",
"@vueuse/nuxt": "^10.1.0",
"eslint": "^8.39.0",
"eslint-plugin-tailwindcss": "^3.11.0",
"nuxt": "^3.4.2",
"nuxt-icon": "^0.3.3"
"@nuxtjs/eslint-module": "^4.1.0",
"@nuxtjs/tailwindcss": "^6.7.0",
"@types/node": "^20",
"@vueuse/components": "^10.1.2",
"@vueuse/core": "^10.1.2",
"@vueuse/nuxt": "^10.1.2",
"eslint": "^8.41.0",
"eslint-plugin-tailwindcss": "^3.12.0",
"nuxt": "^3.5.0",
"nuxt-icon": "^0.4.1"
},
"dependencies": {
"@nuxt/eslint-config": "^0.1.1",
@ -30,8 +30,7 @@
"@xeovalyte/nuxt-xvui": "git+https://gitea.xeovalyte.dev/xeovalyte/nuxt-xvui.git",
"jsonwebtoken": "^9.0.0",
"minecraft-server-util": "^5.4.2",
"mongodb": "^5.3.0",
"socket.io-client": "^4.6.1",
"surrealdb.js": "^0.6.0"
"mongodb": "^5.5.0",
"socket.io-client": "^4.6.1"
}
}

14
web/pages/mod/server.vue Normal file
View File

@ -0,0 +1,14 @@
<template>
<div class="mt-5">
Server Control
</div>
</template>
<script setup>
definePageMeta({
middleware: ["auth"],
moderator: true
})
useHead({ title: 'Server Control | Polarcraft' })
</script>

77
web/pages/mod/users.vue Normal file
View File

@ -0,0 +1,77 @@
<template>
<div class="mt-5">
<Modal v-if="actionModal.open" :title="actionModal.title">
WIP
</Modal>
<h1 class="text-2xl font-bold text-primary">
Users
</h1>
<div class="mx-auto my-10 max-w-4xl">
<h2 class="mb-2 text-xl font-bold text-primary">User Actions</h2>
<div class="w-full rounded border-[1px] border-primary px-3 py-1 text-left">
<table class="w-full table-auto divide-y divide-neutral-500 rounded">
<tr class="border-b-2 border-primary text-gray-200">
<th class="py-1">Username</th>
<th class="py-1">Team</th>
<th class="py-1">MC Linked</th>
<th class="py-1">Actions</th>
</tr>
<tr v-for="user in users" :key="user._id" class="text-gray-200">
<td class="py-1">{{ user.username }}</td>
<td class="py-1">{{ user.team ? teams.filter(a => a._id === user.team.id)[0].name : '-' }}</td>
<td class="py-1">{{ user.minecraft.uuid ? 'True ' : 'False' }}</td>
<td class="space-x-2 py-1">
<span v-if="currentUser.role.admin" class="rounded bg-red-700 px-2 text-red-300 hover:cursor-pointer hover:bg-red-800" @click="openModal(user, 'ban', ban(user))">Ban</span>
<span class="rounded bg-orange-700 px-2 text-orange-300 hover:cursor-pointer hover:bg-orange-800">Suspend</span>
<span class="rounded bg-yellow-700 px-2 text-yellow-300 hover:cursor-pointer hover:bg-yellow-800">Warn</span>
</td>
</tr>
</table>
</div>
</div>
</div>
</template>
<script setup>
definePageMeta({
middleware: ["auth"],
moderator: true
})
useHead({ title: 'Users | Polarcraft' })
const currentUser = useState('user')
const { data: users } = useFetch('/api/auth/getusers')
const { data: teams } = useFetch('/api/team/all')
const actionModal = ref({
open: false,
title: '',
userId: '',
func: null,
})
const openModal = (user, type, func) => {
if (type === 'ban') actionModal.value.title = 'Ban User'
if (type === 'suspend') actionModal.value.title = 'Suspend User'
if (type === 'warn') actionModal.value.title = 'Warn User'
actionModal.value.func = func
actionModal.value.userId = user._id;
actionModal.value.open = true
}
const ban = async (user) => {
try {
const response = await $fetch(`/api/auth/user/${user._id}/ban`, {
reason: "reason"
})
user = response;
} catch (e) {
console.log(e);
useToast().error('Error banning user')
}
}
</script>

View File

@ -1,6 +1,59 @@
<template>
<div class="mt-5 text-primary">
Player Store
<Modal
v-if="storeModal.open"
title="Create new store"
:delete="!storeModal.new"
@submit="submitModal"
@delete="deleteStore"
@close="storeModal.open = false"
>
<Input v-model="storeModal.name" background-class="bg-neutral-800">Store name</Input>
<div class="flex gap-x-5">
<Input v-model="storeModal.coords.x" background-class="bg-neutral-800">Coordinate X</Input>
<Input v-model="storeModal.coords.z" background-class="bg-neutral-800">Coordinate Z</Input>
</div>
<h2 class="font-bold">Items</h2>
<div class="max-h-56 space-y-3 overflow-scroll">
<div v-for="item, index in storeModal.items" :key="item.name" class="flex gap-2">
<Input v-model="item.displayName" background-class="bg-neutral-800">Name</Input>
<Input v-model="item.price" background-class="bg-neutral-800">Price per quantity</Input>
<Button type="danger" @click="storeModal.items.splice(index, 1)"><Icon size="1.5em" name="ph:trash" class="-my-2" /></Button>
</div>
</div>
<Button class="" @click="storeModal.items.push({ displayName: '', price: '' })">Add Item</Button>
</Modal>
<h1 class="pb-10 text-2xl font-bold text-primary">
Player Stores
</h1>
<div class="fixed bottom-10 right-10">
<Button class="fixed" @click="openStoreModal(true)">
Create Store
</Button>
</div>
<div class="flex flex-wrap gap-10">
<div v-for="store in stores" :key="store._id.toString()" class="h-min w-96 rounded-lg bg-neutral-800 text-gray-200 shadow relative">
<div v-if="store.ownerId === user._id" class="absolute right-5 top-5 hover:cursor-pointer" @click="openStoreModal(false, store)">
<Icon name="ph:pencil-simple" size="1.6em" />
</div>
<div class="p-5 pb-3">
<div class="flex items-center">
<img :src="'https://api.mineatar.io/face/' + store.owner.minecraft.uuid + '?scale=3'" class="mr-2 aspect-square rounded">
{{ store.owner.username }}
</div>
<h2 class="text-2xl font-bold text-primary">
{{ store.name }}
<span class="text-sm font-medium">({{ store.coords.x }}, {{ store.coords.z }})</span>
</h2>
</div>
<h2 class="ml-3 text-xl font-bold">Items</h2>
<div class="divide-y divide-neutral-600 rounded-lg bg-neutral-700 px-4 pt-2 pb-2">
<div v-for="item in store.items" :key="item.displayname" class="py-1">
<b>{{ item.displayName }}</b> for <b>{{ item.price }}</b>
</div>
</div>
</div>
</div>
</div>
</template>
@ -9,5 +62,97 @@ definePageMeta({
middleware: ["auth"]
})
const user = useState('user')
useHead({ title: 'Player Stores | Polarcraft' })
const { data: stores } = await useFetch('/api/store');
const storeModal = ref({
open: false,
name: '',
new: false,
id: '',
coords: {
x: '',
z: '',
},
items: [
{ displayName: '', price: '' }
],
})
const openStoreModal = (newStore, store) => {
storeModal.value.open = true;
if (newStore === true) {
storeModal.value.name = '';
storeModal.value.items = [
{ displayName: '', price: '' }
];
storeModal.value.coords = { x: '', z: '' }
storeModal.value.new = true
} else {
storeModal.value.new = false
storeModal.value.items = []
store.items.forEach(item => {
storeModal.value.items.push(item);
})
storeModal.value.name = store.name
storeModal.value.id = store._id
storeModal.value.coords.x = store.coords.x
storeModal.value.coords.z = store.coords.z
}
}
const submitModal = async () => {
try {
const store = await $fetch('/api/store', {
method: 'POST',
body: { name: storeModal.value.name, coords: storeModal.value.coords, items: storeModal.value.items, id: storeModal.value.id || null }
})
if (storeModal.value.id) {
const oldStore = stores.value.filter(a => a._id === storeModal.value.id)[0]
oldStore.name = storeModal.value.name;
oldStore.coords = storeModal.value.coords;
oldStore.items = storeModal.value.items;
} else {
store.owner = user
stores.value.push(store)
}
storeModal.value.open = false
useToast().success('Succesvol store aangemaakt')
} catch (e) {
console.log(e)
useToast().error(e.statusMessage)
}
}
const deleteStore = async () => {
try {
await $fetch('/api/store', {
method: 'DELETE',
body: { id: storeModal.value.id }
})
stores.value = stores.value.filter(a => a._id !== storeModal.value.id)
storeModal.value.open = false
useToast().success('Succesvol store verwijderd')
} catch (e) {
console.log(e)
useToast().error(e.statusMessage)
}
}
</script>

View File

@ -0,0 +1,13 @@
export default defineEventHandler(async (event) => {
const user = await getAuth(event)
if (!user.role.moderator) return createError({ statusCode: 403, statusMessage: 'Gebruiker heeft geen toegang tot alle gebruikers' })
const usersColl = db.collection('users');
const cursor = usersColl.find()
const users = await cursor.toArray()
return users
});

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 }, teamInvites: [] } }, { upsert: true })
await coll.updateOne({ 'discord.id': userResult.id }, { $set: doc, $setOnInsert: { minecraft: { uuid: null, username: null }, teamInvites: [], role: {} } }, { upsert: true })
const token = createToken(tokenResponseData.access_token, tokenResponseData.refresh_token, tokenResponseData.expires_in, userResult.id )

View File

@ -0,0 +1,25 @@
import { ObjectId } from "mongodb";
export default defineEventHandler(async (event) => {
const { reason } = await readBody(event)
const currentUser = await getAuth(event)
const userId = event.context.params.id;
if (!reason) return createError({ statusCode: 400, statusMessage: 'Reason is vereist' })
if (!currentUser.role.admin) return createError({ statusCode: 403, statusMessage: 'Geen toegang om gebruiker te bannen' })
const usersColl = db.collection('users')
const user = usersColl.findOneAndUpdate({ _id: new ObjectId(userId)}, { $set: { banned: { reason: reason, date: new Date() } } })
if (!user.value) return createError({ statusCode: 500, statusMessage: 'Error tijdens het updaten van de gebruiker' })
await $fetch(config.discordHost + '/user/ban', {
method: 'POST',
body: { reason: reason, discordId: user.discord.id }
})
await sendRconCommand(`ban ${user.value.minecraft.uuid} ${reason}`)
return user
});

View File

@ -1,5 +1,3 @@
import { applyUsername } from "~/server/utils/auth";
export default defineEventHandler(async (event) => {
const user = await getAuth(event)

View File

@ -0,0 +1,17 @@
import { ObjectId } from "mongodb";
export default defineEventHandler(async (event) => {
const { id } = await readBody(event)
const user = await getAuth(event)
const storesColl = db.collection('stores');
const result = await storesColl.deleteOne({ _id: new ObjectId(id) })
if (result.deletedCount === 1) {
return result
} else {
throw createError({ statusCode: 500, statusMessage: 'Error tijdens het verwijderen van de store' })
}
});

View File

@ -0,0 +1,21 @@
export default defineEventHandler(async () => {
const storesColl = db.collection('stores');
const cursor = storesColl.aggregate([
{
$lookup:
{
from: "users",
localField: "ownerId",
foreignField: "_id",
as: "owner",
},
},
{
$unwind: "$owner",
},
])
return await cursor.toArray()
});

View File

@ -0,0 +1,13 @@
import { ObjectId } from "mongodb";
export default defineEventHandler(async (event) => {
const { coords, name, items, id } = await readBody(event)
const user = await getAuth(event)
const storesColl = db.collection('stores');
const { value: store } = await storesColl.findOneAndUpdate({ _id: id ? new ObjectId(id) : new ObjectId() }, { $set: { coords: coords, name: name, items: items, ownerId: user._id, } }, { returnDocument: 'after', upsert: true })
return store
});