Skip to content
Closed
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
4 changes: 3 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@
"frontend/i18n",
"frontend/stats",
"frontend/join",
"frontend/map"
"frontend/map",
"frontend/assets",
"dash/team"
],
"js/ts.tsdk.path": "node_modules\\typescript\\lib"
}
297 changes: 297 additions & 0 deletions apps/dashboard/src/actions/buildTeams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ function parseSocials(formData: FormData): Array<{ id?: string; name: string; ur
.map(([, social]) => social)
.filter((social) => social.id || social.name.trim() || social.url.trim());
}

export const adminTransferTeam = async (
prevState: any,
{
Expand Down Expand Up @@ -488,6 +489,67 @@ export const removeMember = async ({
revalidatePath(`/team/${buildTeam.slug}/members`);
};

export const removeMembers = async ({
userId,
removeIds,
buildTeamSlug,
reason,
notifyUsers = true,
}: {
userId: string;
removeIds: string[];
reason?: string;
buildTeamSlug?: string;
notifyUsers?: boolean;
}) => {
const userHasPermission = await prisma.userPermission.findFirst({
where: {
OR: [
{
user: { ssoId: userId },
permissionId: 'permission.remove',
buildTeam: { slug: buildTeamSlug },
},
{
user: { ssoId: userId },
permissionId: 'permission.remove',
buildTeamId: null,
},
],
},
});

if (!userHasPermission) {
throw Error('You do not have permission to remove members from this Build Team');
}

const membersToRemove = await prisma.user.findMany({
where: { ssoId: { in: removeIds } },
select: { discordId: true },
});

const buildTeam = await prisma.buildTeam.update({
where: { slug: buildTeamSlug },
data: {
members: {
disconnect: removeIds.map((id) => ({ ssoId: id })),
},
},
});

if (notifyUsers) {
sendBotMessage(
`## <:warn:1441532241628102686> You have been removed from ${buildTeam.name}` +
`\n\nThe Build Team \`${buildTeam.name}\` has removed you as a builder from their team. This means you are no longer part of their group and will not be able to create and manage claims for them. Additionally, you will not be able to apply to this Build Team again as long as your past application status is set to 'Accepted'.` +
(reason ? ` The team has provided the following reason for your removal: \n \n${reason}` : '') +
'\n\nIf you believe this was a mistake, please reach out to the Build Team directly for more information.',
membersToRemove.map((member) => member.discordId!).filter((id): id is string => !!id),
);
}

revalidatePath(`/team/${buildTeam.slug}/members`);
};

export const addMember = async ({
userId,
addId,
Expand Down Expand Up @@ -554,6 +616,93 @@ export const addMember = async ({
revalidatePath(`/team/${buildTeam.slug}/members`);
};

export const setMemberPermissions = async ({
userId,
changeId,
permissions,
buildTeamSlug,
notifyUser = true,
}: {
userId: string;
changeId: string;
permissions: string[];
buildTeamSlug?: string;
notifyUser?: boolean;
}) => {
const userHasPermission = await prisma.userPermission.findFirst({
where: {
OR: [
{
user: { ssoId: userId },
permissionId: 'permission.add',
buildTeam: { slug: buildTeamSlug },
},
{
user: { ssoId: userId },
permissionId: 'permission.add',
buildTeamId: null,
},
],
},
});

const buildTeam = await prisma.buildTeam.findFirst({
where: { slug: buildTeamSlug },
select: { id: true, name: true, slug: true },
});

if (!userHasPermission) {
throw Error('You do not have permission to change permissions on this Build Team');
}

if (!buildTeam) {
throw Error('Build Team not found');
}

const userToChange = await prisma.user.findFirst({
where: { OR: [{ ssoId: changeId }, { id: changeId }, { discordId: changeId }, { username: changeId }] },
include: {
permissions: {
where: { buildTeam: { slug: buildTeamSlug } },
include: { permission: true },
},
},
});

if (!userToChange) {
throw Error('User to set permissions for not found');
}

await prisma.userPermission.deleteMany({
where: {
userId: userToChange.id,
buildTeamId: (await prisma.buildTeam.findFirst({ where: { slug: buildTeamSlug }, select: { id: true } }))?.id,
NOT: { permissionId: { in: permissions } },
},
});
await prisma.userPermission.createMany({
data: permissions
.filter((permission) => !userToChange.permissions.some((p) => p.permissionId === permission))
.map((permission) => ({
userId: userToChange.id,
buildTeamId: buildTeam.id,
permissionId: permission,
})),
});

if (notifyUser) {
sendBotMessage(
`## <:unban:1441532232627130548> Your permissions for ${buildTeam.name} changed` +
`\n\nYour permissions for the BuildTeam \`${buildTeam.name}\` have been changed. You now have the following additional permissions:` +
`\n\n${permissions.length > 0 ? permissions.map((p) => `- ${p}`).join('\n') : ' `none`'}`,
[userToChange?.discordId!],
);
// TODO: possibly add discord role if this is the first BT the user joins
}

revalidatePath(`/team/${buildTeam.slug}/members`);
};

export const addApplicationResponseTemplate = async ({
userId,
buildTeamSlug,
Expand Down Expand Up @@ -862,6 +1011,154 @@ export const applyToBuildTeam = async (
return;
};

export const saveBuildTeamApplicationQuestions = async ({ userId, buildTeamSlug, questions }: any) => {
if (!Array.isArray(questions)) {
throw Error('Invalid payload: expected a list of questions');
}

const userHasPermission = await prisma.userPermission.findFirst({
where: {
OR: [
{
user: { ssoId: userId },
permissionId: 'team.application.edit',
buildTeam: { slug: buildTeamSlug },
},
{
user: { ssoId: userId },
permissionId: 'team.application.edit',
buildTeamId: null,
},
],
},
});

if (!userHasPermission) {
throw Error('You do not have permission to edit application questions on this Build Team');
}

const team = await prisma.buildTeam.findFirst({ where: { slug: buildTeamSlug }, select: { id: true, slug: true } });
if (!team) {
throw Error('Build Team not found');
}

function sanitizeQuestionInput(q: any) {
if (!q || typeof q.id !== 'string') throw Error('A question is missing a valid id');
if (!q || typeof q.title !== 'string') throw Error('A question is missing a valid title');
if (!Object.values(ApplicationQuestionType).includes(q.type))
throw Error(`Unsupported question type: ${String(q.type)}`);

return {
id: q.id,
title: (q.title || '').trim(),
subtitle: (q.subtitle || '').trim(),
placeholder: q.placeholder || '',
required: Boolean(q.required),
type: q.type,
icon: (q.icon || 'question-mark').trim(),
additionalData: q.additionalData ?? {},
sort: Number.isFinite(q.sort) ? Math.trunc(q.sort) : 0,
trial: Boolean(q.trial),
};
}

const sanitizedQuestions = questions.map((question) => sanitizeQuestionInput(question));

await prisma.$transaction(async (tx) => {
// if (deleteIds.length > 0) {
// await tx.applicationQuestion.deleteMany({ where: { buildTeamId: team.id, id: { in: deleteIds } } });
// }

for (const question of sanitizedQuestions) {
const existingQuestion = await tx.applicationQuestion.findUnique({
where: { id: question.id },
select: { id: true, buildTeamId: true },
});

if (existingQuestion && existingQuestion.buildTeamId !== team.id) {
throw Error('A question id does not belong to this Build Team');
}

if (existingQuestion) {
await tx.applicationQuestion.update({
where: { id: question.id },
data: {
title: question.title,
subtitle: question.subtitle || '',
placeholder: question.placeholder || '',
required: question.required ?? false,
type: question.type,
icon: question.icon || 'question-mark',
additionalData: question.additionalData ?? {},
sort: question.sort,
trial: question.trial ?? false,
},
});
continue;
}

await tx.applicationQuestion.create({
data: {
id: question.id,
title: question.title,
subtitle: question.subtitle || '',
placeholder: question.placeholder || '',
required: question.required ?? false,
type: question.type,
icon: question.icon || 'question-mark',
additionalData: question.additionalData ?? {},
sort: question.sort,
trial: question.trial ?? false,
buildTeam: { connect: { id: team.id } },
},
});
}
});

revalidatePath(`/team/${team.slug}/questions`);
revalidatePath(`/apply/${team.slug}`);

return;
};

export const deleteClaim = async ({
userId,
removeId,
buildTeamSlug,
}: {
userId: string;
removeId: string;
buildTeamSlug: string;
}) => {
const userHasPermission = await prisma.userPermission.findFirst({
where: {
OR: [
{
user: { ssoId: userId },
permissionId: 'team.claim.list',
buildTeam: { slug: buildTeamSlug },
},
{
user: { ssoId: userId },
permissionId: 'team.claim.list',
buildTeamId: null,
},
],
},
});

if (!userHasPermission) {
throw Error('You do not have permission to delete claims from this Build Team');
}

const claim = await prisma.claim.delete({
where: { buildTeam: { slug: buildTeamSlug }, id: removeId },
});

revalidatePath(`/team/${buildTeamSlug}/claims`);
redirect(`/team/${buildTeamSlug}/claims`);
};

/**
* Replaces placeholders to actual data in discord messages to users
* @param message Message with placeholders
Expand Down
10 changes: 10 additions & 0 deletions apps/dashboard/src/app/(sideNavbar)/am/claims/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ export default async function Page({ searchParams }: { searchParams: Promise<{ p
{ owner: { minecraft: { contains: searchQuery || undefined } } },
{ owner: { discordId: { contains: searchQuery || undefined } } },
{ owner: { ssoId: { contains: searchQuery || undefined } } },
{ buildTeam: { name: { contains: searchQuery || undefined } } },
{ buildTeam: { slug: { contains: searchQuery || undefined } } },
{ buildTeam: { location: { contains: searchQuery || undefined } } },
{ buildTeam: { invite: { contains: searchQuery || undefined } } },
{ buildTeam: { ip: { contains: searchQuery || undefined } } },
],
}
: undefined,
Expand All @@ -48,6 +53,11 @@ export default async function Page({ searchParams }: { searchParams: Promise<{ p
{ owner: { minecraft: { contains: searchQuery || undefined } } },
{ owner: { discordId: { contains: searchQuery || undefined } } },
{ owner: { ssoId: { contains: searchQuery || undefined } } },
{ buildTeam: { name: { contains: searchQuery || undefined } } },
{ buildTeam: { slug: { contains: searchQuery || undefined } } },
{ buildTeam: { location: { contains: searchQuery || undefined } } },
{ buildTeam: { invite: { contains: searchQuery || undefined } } },
{ buildTeam: { ip: { contains: searchQuery || undefined } } },
],
}
: undefined,
Expand Down
Loading