Add permissions

This commit is contained in:
Kalle
2022-05-26 19:48:16 +03:00
parent 300b4599f9
commit a4b839d55a
7 changed files with 139 additions and 22 deletions

View File

@@ -23,7 +23,8 @@ sendou.ink/
│ ├── hooks/ -- React hooks
│ ├── routes/ -- Routes see: https://remix.run/docs/en/v1/guides/routing
│ ├── styles/ -- All .css files of the project for styling
── utils/ -- Random helper functions used in many places
── utils/ -- Random helper functions used in many places
│ └── permissions.ts / -- What actions are allowed. Separated by frontend and backend as frontend has constraints based on what user sees.
├── migrations/ -- Database migrations
├── cypress/ -- see: https://docs.cypress.io/guides/core-concepts/writing-and-organizing-tests#Folder-structure
├── public/ -- Images, built assets etc. static files to be served as is

View File

@@ -2,6 +2,7 @@ import { Popover as HeadlessPopover } from "@headlessui/react";
import * as React from "react";
import { usePopper } from "react-popper";
// xxx: gets weird border on click
export function Popover({
children,
trigger,

View File

@@ -27,7 +27,7 @@ export function upcomingVoting(now: Date): MonthYear {
}
/** Range of first Friday of a month to the following Monday (this range is when voting is active) */
function monthsVotingRange({ month, year }: MonthYear) {
export function monthsVotingRange({ month, year }: MonthYear) {
const startDate = new Date(Date.UTC(year, month, 1, 10));
while (startDate.getDay() !== 5) {

76
app/permissions.ts Normal file
View File

@@ -0,0 +1,76 @@
import type * as plusSuggestions from "~/db/models/plusSuggestions.server";
import type { User } from "./db/types";
import { allTruthy } from "./utils/arrays";
interface CanAddCommentToSuggestionFEArgs {
user?: Pick<User, "id">;
allSuggestions: plusSuggestions.FindResult;
target: Pick<User, "id" | "plusTier">;
}
export function canAddCommentToSuggestionFE(
args: CanAddCommentToSuggestionFEArgs
) {
return !alreadyCommentedByUser(args);
}
interface CanAddCommentToSuggestionBEArgs
extends CanAddCommentToSuggestionFEArgs {
user?: Pick<User, "id" | "plusTier">;
targetPlusTier: number;
}
export function canAddCommentToSuggestionBE({
user,
targetPlusTier,
allSuggestions,
target,
}: CanAddCommentToSuggestionBEArgs) {
return allTruthy([
canAddCommentToSuggestionFE({ user, allSuggestions, target }),
playerAlreadySuggested({ allSuggestions, target }),
targetPlusTierIsSmallerOrEqual({ user, targetPlusTier }),
]);
}
// TODO: needed for new suggestions
// function votingIsActive() {
// const now = new Date();
// const { endDate, startDate } = monthsVotingRange({
// month: now.getMonth(),
// year: now.getFullYear(),
// });
// return (
// now.getTime() >= startDate.getTime() && now.getTime() <= endDate.getTime()
// );
// }
function alreadyCommentedByUser({
user,
allSuggestions,
target,
}: CanAddCommentToSuggestionFEArgs) {
return Boolean(
allSuggestions
.find(({ tier }) => tier === target.plusTier)
?.users.find((u) => u.info.id === target.id)
?.suggestions.some((s) => s.author.id === user?.id)
);
}
function playerAlreadySuggested({
allSuggestions,
target,
}: Pick<CanAddCommentToSuggestionBEArgs, "allSuggestions" | "target">) {
return Boolean(
allSuggestions
.find(({ tier }) => tier === target.plusTier)
?.users.find((u) => u.info.id === target.id)
);
}
function targetPlusTierIsSmallerOrEqual({
user,
targetPlusTier,
}: Pick<CanAddCommentToSuggestionBEArgs, "user" | "targetPlusTier">) {
return user?.plusTier && user.plusTier <= targetPlusTier;
}

View File

@@ -14,6 +14,7 @@ import { upcomingVoting } from "~/core/plus";
import { db } from "~/db";
import type * as plusSuggestions from "~/db/models/plusSuggestions.server";
import { useUser } from "~/hooks/useUser";
import { canAddCommentToSuggestionFE } from "~/permissions";
import styles from "~/styles/plus.css";
import { databaseTimestampToDate } from "~/utils/dates";
import { makeTitle, requireUser } from "~/utils/remix";
@@ -134,7 +135,7 @@ function SuggestedForInfo() {
}
function SuggestedUser({
user,
user: suggestedUser,
tier,
}: {
user: Unpacked<
@@ -142,36 +143,44 @@ function SuggestedUser({
>;
tier: number;
}) {
const commentPageUrl = () =>
`comment?${new URLSearchParams({
id: String(user.info.id),
tier: String(tier),
}).toString()}`;
const data = useLoaderData<PlusSuggestionsLoaderData>();
const user = useUser();
const commentPageUrl = `comment?${new URLSearchParams({
id: String(suggestedUser.info.id),
tier: String(tier),
}).toString()}`;
return (
<div className="stack md">
<div className="plus__suggested-user-info">
<Avatar
discordAvatar={user.info.discordAvatar}
discordId={user.info.discordId}
discordAvatar={suggestedUser.info.discordAvatar}
discordId={suggestedUser.info.discordId}
size="md"
/>
<h2>{user.info.discordName}</h2>
<LinkButton
className="plus__comment-button"
tiny
variant="outlined"
to={commentPageUrl()}
>
Comment
</LinkButton>
<h2>{suggestedUser.info.discordName}</h2>
{canAddCommentToSuggestionFE({
user,
allSuggestions: data.suggestions!,
target: { id: suggestedUser.info.id, plusTier: tier },
}) ? (
<LinkButton
className="plus__comment-button"
tiny
variant="outlined"
to={commentPageUrl}
>
Comment
</LinkButton>
) : null}
</div>
<details>
<summary className="plus__view-comments-action">
Comments ({user.suggestions.length})
Comments ({suggestedUser.suggestions.length})
</summary>
<div className="stack sm mt-2">
{user.suggestions.map((s) => (
{suggestedUser.suggestions.map((s) => (
// xxx: white-space: pre-wrap?
<fieldset key={s.author.id}>
<legend>{discordFullName(s.author)}</legend>

View File

@@ -12,8 +12,28 @@ import { PLUS_SUGGESTIONS_PAGE } from "~/utils/urls";
import type { PlusSuggestionsLoaderData } from "../suggestions";
import * as React from "react";
import { PlUS_SUGGESTION_COMMENT_MAX_LENGTH } from "~/constants";
import type { ActionFunction } from "@remix-run/node";
import { z } from "zod";
import { parseRequestFormData, requireUser } from "~/utils/remix";
import { canAddCommentToSuggestionFE } from "~/permissions";
import { useUser } from "~/hooks/useUser";
const commentActionSchema = z.object({
comment: z.string().max(PlUS_SUGGESTION_COMMENT_MAX_LENGTH),
});
export const action: ActionFunction = async ({ request }) => {
const data = await parseRequestFormData({
request,
schema: commentActionSchema,
});
const user = await requireUser(request);
return null;
};
export default function PlusCommentModalPage() {
const user = useUser();
const matches = useMatches();
const navigate = useNavigate();
const [searchParams] = useSearchParams();
@@ -26,7 +46,14 @@ export default function PlusCommentModalPage() {
?.find(({ tier }) => tier === tierSuggestedTo)
?.users.find((u) => u.info.id === userBeingCommentedId);
if (!userBeingCommented) {
if (
!userBeingCommented ||
!canAddCommentToSuggestionFE({
user,
allSuggestions: data.suggestions!,
target: { id: userBeingCommentedId, plusTier: tierSuggestedTo },
})
) {
return <Redirect to={PLUS_SUGGESTIONS_PAGE} />;
}

3
app/utils/arrays.ts Normal file
View File

@@ -0,0 +1,3 @@
export function allTruthy(arr: unknown[]) {
return arr.every(Boolean);
}