Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified prisma/dev.db
Binary file not shown.
Binary file added prisma/dev.db-journal
Binary file not shown.
9 changes: 9 additions & 0 deletions prisma/migrations/20250414211601_config_model/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
-- CreateTable
CREATE TABLE "Config" (
"guild_id" TEXT NOT NULL PRIMARY KEY,
"setting_type" TEXT NOT NULL,
"value" TEXT NOT NULL
);

-- CreateIndex
CREATE INDEX "Config_guild_id_setting_type_idx" ON "Config"("guild_id", "setting_type");
21 changes: 21 additions & 0 deletions prisma/migrations/20250414212408_update_config_id/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
Warnings:

- The primary key for the `Config` table will be changed. If it partially fails, the table could be left without primary key constraint.

*/
-- RedefineTables
PRAGMA defer_foreign_keys=ON;
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_Config" (
"guild_id" TEXT NOT NULL,
"setting_type" TEXT NOT NULL,
"value" TEXT NOT NULL,

PRIMARY KEY ("guild_id", "setting_type")
);
INSERT INTO "new_Config" ("guild_id", "setting_type", "value") SELECT "guild_id", "setting_type", "value" FROM "Config";
DROP TABLE "Config";
ALTER TABLE "new_Config" RENAME TO "Config";
PRAGMA foreign_keys=ON;
PRAGMA defer_foreign_keys=OFF;
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
Warnings:

- The primary key for the `Config` table will be changed. If it partially fails, the table could be left without primary key constraint.
- You are about to drop the column `guild_id` on the `Config` table. All the data in the column will be lost.
- You are about to drop the column `setting_type` on the `Config` table. All the data in the column will be lost.
- Added the required column `guildId` to the `Config` table without a default value. This is not possible if the table is not empty.
- Added the required column `settingType` to the `Config` table without a default value. This is not possible if the table is not empty.

*/
-- RedefineTables
PRAGMA defer_foreign_keys=ON;
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_Config" (
"guildId" TEXT NOT NULL,
"settingType" TEXT NOT NULL,
"value" TEXT NOT NULL,

PRIMARY KEY ("guildId", "settingType")
);
INSERT INTO "new_Config" ("value") SELECT "value" FROM "Config";
DROP TABLE "Config";
ALTER TABLE "new_Config" RENAME TO "Config";
PRAGMA foreign_keys=ON;
PRAGMA defer_foreign_keys=OFF;
80 changes: 44 additions & 36 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -13,28 +13,28 @@ model DiscordUser {
username String
discriminator String?
avatar String?
roles String // Stored as JSON string of role IDs
roles String // Stored as JSON string of role IDs
joinedAt DateTime @default(now())
lastSeen DateTime @default(now())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt

// Relations
teams TeamMember[]
events EventParticipant[]
teamsLed Team[] // Teams where this user is the leader
submissions Submission[]
teams TeamMember[]
events EventParticipant[]
teamsLed Team[] // Teams where this user is the leader
submissions Submission[]
}

model Event {
id String @id @default(uuid())
id String @id @default(uuid())
name String
description String?
startDate DateTime
endDate DateTime?
status String @default("PLANNED") // PLANNED, ONGOING, COMPLETED, CANCELLED
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
status String @default("PLANNED") // PLANNED, ONGOING, COMPLETED, CANCELLED
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt

// Relations
participants EventParticipant[]
Expand All @@ -48,7 +48,7 @@ model Team {
description String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
leaderId String // ID of the team leader
leaderId String // ID of the team leader
leader DiscordUser @relation(fields: [leaderId], references: [id])

// Relations
Expand All @@ -59,46 +59,54 @@ model Team {

// Junction tables for many-to-many relationships
model TeamMember {
id String @id @default(uuid())
userId String
teamId String
role String @default("MEMBER") // LEADER, MEMBER, etc.
joinedAt DateTime @default(now())
user DiscordUser @relation(fields: [userId], references: [id])
team Team @relation(fields: [teamId], references: [id])
id String @id @default(uuid())
userId String
teamId String
role String @default("MEMBER") // LEADER, MEMBER, etc.
joinedAt DateTime @default(now())
user DiscordUser @relation(fields: [userId], references: [id])
team Team @relation(fields: [teamId], references: [id])

@@unique([userId, teamId])
}

model EventParticipant {
id String @id @default(uuid())
userId String
eventId String
status String @default("REGISTERED") // REGISTERED, INTERESTED, UNPAID
user DiscordUser @relation(fields: [userId], references: [id])
event Event @relation(fields: [eventId], references: [id])
id String @id @default(uuid())
userId String
eventId String
status String @default("REGISTERED") // REGISTERED, INTERESTED, UNPAID
user DiscordUser @relation(fields: [userId], references: [id])
event Event @relation(fields: [eventId], references: [id])

@@unique([userId, eventId])
}

model Submission {
id String @id @default(uuid())
name String // Name of the submitted item
value String // Value of the submitted item
proofUrl String // URL to the proof image
status String @default("PENDING") // PENDING, APPROVED, REJECTED
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
id String @id @default(uuid())
name String // Name of the submitted item
value String // Value of the submitted item
proofUrl String // URL to the proof image
status String @default("PENDING") // PENDING, APPROVED, REJECTED
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt

// Relations
userId String // ID of the user who submitted
eventId String // ID of the event this submission is for
teamId String // ID of the team this submission belongs to
user DiscordUser @relation(fields: [userId], references: [id])
event Event @relation(fields: [eventId], references: [id])
team Team @relation(fields: [teamId], references: [id])
userId String // ID of the user who submitted
eventId String // ID of the event this submission is for
teamId String // ID of the team this submission belongs to
user DiscordUser @relation(fields: [userId], references: [id])
event Event @relation(fields: [eventId], references: [id])
team Team @relation(fields: [teamId], references: [id])

@@index([userId])
@@index([eventId])
@@index([teamId])
}

model Config {
guildId String
settingType String
value String

@@id([guildId, settingType])
}
34 changes: 34 additions & 0 deletions src/commands/admin/adminrole.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { ChatInputCommandInteraction, PermissionFlagsBits, SlashCommandBuilder } from 'discord.js';
import { ConfigService } from '#services/configService.js';
import logger from '#utils/logger.js';

export const data = new SlashCommandBuilder()
.setName('adminrole')
.setDescription('Get or set admin role')
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator)
.addRoleOption((option) =>
option.setName('role').setDescription('Role to be set as admin role').setRequired(false)
);

/**
* @param {ChatInputCommandInteraction} interaction
*/
export async function execute(interaction) {
const role = interaction.options.getRole('role');

// if role doesn't exist, get role channel if it exists
if (!role) {
const currentRole = await ConfigService.getAdminRole(interaction.guildId);
if (currentRole) {
await interaction.reply(`Current admin role: <@&${currentRole}>`);
} else {
await interaction.reply('No admin role set.');
}
return;
}

// if role exists, set it as the admin role
await ConfigService.setAdminRole(interaction.guildId, role.id);
await interaction.reply(`Admin role set to <@&${role.id}>`);
logger.info(`Admin role set to ${role.id} for guild ${interaction.guildId}`);
}
53 changes: 53 additions & 0 deletions src/commands/admin/approvechannel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { ChannelType, ChatInputCommandInteraction, SlashCommandBuilder } from 'discord.js';
import { ConfigService } from '#services/configService.js';
import logger from '#utils/logger.js';

export const data = new SlashCommandBuilder()
.setName('approvechannel')
.setDescription('Get or set the approval submission channel')
.addChannelOption((option) =>
option
.setName('channel')
.setDescription('Moderated channel for submissions to be forwarded to')
.setRequired(false)
.addChannelTypes(ChannelType.GuildText)
);

/**
* @param {ChatInputCommandInteraction} interaction
*/
export async function execute(interaction) {
const channel = interaction.options.getChannel('channel');

// if channel doesn't exist, get current channel if it exists
if (!channel) {
const currentChannel = await ConfigService.getApprovalChannel(interaction.guildId);
if (currentChannel) {
await interaction.reply(`Current approval channel: <#${currentChannel}>`);
} else {
await interaction.reply('No approval channel set.');
}
return;
}

// Check if user has admin permissions
// Check custom role first
if (!interaction.member.permissions.has('Administrator')) {
const customRole = await ConfigService.getAdminRole(interaction.guildId);
if (customRole) {
const member = await interaction.guild.members.fetch(interaction.user.id);
if (!member.roles.cache.has(customRole)) {
await interaction.reply('You do not have permission to set the approval channel.');
return;
}
} else {
await interaction.reply('You do not have permission to set the approval channel.');
return;
}
}

// if channel exists, set it as the approval channel
await ConfigService.setApprovalChannel(interaction.guildId, channel.id);
await interaction.reply(`Approval channel set to <#${channel.id}>`);
logger.info(`Approval channel set to ${channel.id} for guild ${interaction.guildId}`);
}
4 changes: 4 additions & 0 deletions src/constants/settings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const SettingTypes = {
APPROVAL_CHANNEL: 'approval_channel',
ADMIN_ROLE: 'admin_role',
};
Loading