mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-08-22 19:16:09 -05:00
Add permissions
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
76
app/permissions.ts
Normal 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;
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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
3
app/utils/arrays.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export function allTruthy(arr: unknown[]) {
|
||||
return arr.every(Boolean);
|
||||
}
|
||||
Reference in New Issue
Block a user