mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-03-21 18:04:39 -05:00
134 lines
3.4 KiB
TypeScript
134 lines
3.4 KiB
TypeScript
import type { ActionFunctionArgs } from "react-router";
|
|
import { ASSOCIATION } from "~/features/associations/associations-constants";
|
|
import { associationsPageActionSchema } from "~/features/associations/associations-schemas";
|
|
import { requireUser } from "~/features/auth/core/user.server";
|
|
import { requirePermission } from "~/modules/permissions/guards.server";
|
|
import {
|
|
badRequestIfFalsy,
|
|
errorToastIfFalsy,
|
|
parseRequestPayload,
|
|
successToast,
|
|
} from "~/utils/remix.server";
|
|
import { assertUnreachable } from "~/utils/types";
|
|
import * as AssociationRepository from "../AssociationRepository.server";
|
|
|
|
export const action = async ({ request }: ActionFunctionArgs) => {
|
|
const user = await requireUser(request);
|
|
const data = await parseRequestPayload({
|
|
request,
|
|
schema: associationsPageActionSchema,
|
|
});
|
|
|
|
switch (data._action) {
|
|
case "REMOVE_MEMBER": {
|
|
await validateHasManagePermissions({
|
|
user,
|
|
associationId: data.associationId,
|
|
});
|
|
|
|
errorToastIfFalsy(
|
|
data.userId !== user.id,
|
|
"Cannot remove yourself from the association",
|
|
);
|
|
|
|
await AssociationRepository.removeMember({
|
|
userId: data.userId,
|
|
associationId: data.associationId,
|
|
});
|
|
|
|
break;
|
|
}
|
|
case "DELETE_ASSOCIATION": {
|
|
await validateHasManagePermissions({
|
|
user,
|
|
associationId: data.associationId,
|
|
});
|
|
|
|
await AssociationRepository.del(data.associationId);
|
|
|
|
break;
|
|
}
|
|
case "REFRESH_INVITE_CODE": {
|
|
await validateHasManagePermissions({
|
|
user,
|
|
associationId: data.associationId,
|
|
});
|
|
|
|
await AssociationRepository.refreshInviteCode(data.associationId);
|
|
|
|
return successToast("Invite code reset");
|
|
}
|
|
case "JOIN_ASSOCIATION": {
|
|
const associationToJoin = badRequestIfFalsy(
|
|
await AssociationRepository.findByInviteCode(data.inviteCode, {
|
|
withMembers: true,
|
|
}),
|
|
);
|
|
errorToastIfFalsy(
|
|
associationToJoin.members?.every((member) => member.id !== user.id),
|
|
"You are already a member of this association",
|
|
);
|
|
errorToastIfFalsy(
|
|
associationToJoin.members!.length <
|
|
ASSOCIATION.MAX_ASSOCIATION_MEMBER_COUNT,
|
|
"Association is full",
|
|
);
|
|
|
|
const maxAssociationCount = user.roles.includes("SUPPORTER")
|
|
? ASSOCIATION.MAX_COUNT_SUPPORTER
|
|
: ASSOCIATION.MAX_COUNT_REGULAR_USER;
|
|
|
|
errorToastIfFalsy(
|
|
(await AssociationRepository.findByMemberUserId(user.id)).actual
|
|
.length < maxAssociationCount,
|
|
`Regular users can only be a member of ${maxAssociationCount} associations (supporters ${ASSOCIATION.MAX_COUNT_SUPPORTER})`,
|
|
);
|
|
|
|
await AssociationRepository.addMember({
|
|
userId: user.id,
|
|
associationId: associationToJoin.id,
|
|
});
|
|
|
|
break;
|
|
}
|
|
case "LEAVE_ASSOCIATION": {
|
|
const association = badRequestIfFalsy(
|
|
await AssociationRepository.findById(data.associationId, {
|
|
withMembers: true,
|
|
}),
|
|
);
|
|
|
|
errorToastIfFalsy(
|
|
!association.permissions.MANAGE.includes(user.id),
|
|
"You cannot leave an association you manage",
|
|
);
|
|
|
|
await AssociationRepository.removeMember({
|
|
userId: user.id,
|
|
associationId: data.associationId,
|
|
});
|
|
|
|
return successToast("Left association");
|
|
}
|
|
default: {
|
|
assertUnreachable(data);
|
|
}
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
async function validateHasManagePermissions({
|
|
user,
|
|
associationId,
|
|
}: {
|
|
user: { id: number };
|
|
associationId: number;
|
|
}) {
|
|
const association = badRequestIfFalsy(
|
|
await AssociationRepository.findById(associationId, { withMembers: true }),
|
|
);
|
|
|
|
requirePermission(association, "MANAGE", user);
|
|
}
|