From 007bdfb232712505758c87840830621b03d92baa Mon Sep 17 00:00:00 2001 From: tobi Date: Fri, 1 Nov 2024 18:14:29 +0100 Subject: [PATCH] aaaaaaaaaaaa --- .../processing/admin/domainpermissiondraft.go | 36 +++---- internal/util/ptr.go | 7 ++ web/source/settings/components/error.tsx | 6 +- .../{username.tsx => username-lozenge.tsx} | 95 ++++++++++++++++++- .../query/admin/domain-permissions/drafts.ts | 70 ++++++++++++++ web/source/settings/lib/util/index.ts | 13 +++ web/source/settings/style.css | 13 +++ .../admin/http-header-permissions/detail.tsx | 68 ++++--------- .../moderation/accounts/pending/index.tsx | 4 +- .../moderation/accounts/search/index.tsx | 4 +- .../domain-permissions/drafts/detail.tsx | 57 ++--------- .../domain-permissions/drafts/index.tsx | 53 ++++++++++- .../domain-permissions/drafts/new.tsx | 21 +++- .../views/moderation/reports/detail.tsx | 8 +- .../views/moderation/reports/search.tsx | 6 +- 15 files changed, 323 insertions(+), 138 deletions(-) rename web/source/settings/components/{username.tsx => username-lozenge.tsx} (51%) diff --git a/internal/processing/admin/domainpermissiondraft.go b/internal/processing/admin/domainpermissiondraft.go index 210417d2f..36d74cd26 100644 --- a/internal/processing/admin/domainpermissiondraft.go +++ b/internal/processing/admin/domainpermissiondraft.go @@ -32,6 +32,7 @@ "github.com/superseriousbusiness/gotosocial/internal/id" "github.com/superseriousbusiness/gotosocial/internal/log" "github.com/superseriousbusiness/gotosocial/internal/paging" + "github.com/superseriousbusiness/gotosocial/internal/util" ) // DomainPermissionDraftGet returns one @@ -174,7 +175,7 @@ func (p *Processor) DomainPermissionDraftAccept( existing gtsmodel.DomainPermission ) - // Check if existing entry. + // Try to get existing entry. switch permDraft.PermissionType { case gtsmodel.DomainPermissionBlock: existing, err = p.state.DB.GetDomainBlock( @@ -193,6 +194,15 @@ func (p *Processor) DomainPermissionDraftAccept( return nil, "", gtserror.NewErrorInternalError(err) } + // Check if we got existing entry. + existed := !util.IsNil(existing) + if existed && !overwrite { + // Domain permission exists and we shouldn't + // overwrite it, leave everything alone. + const text = "a domain permission already exists with this permission type and domain" + return nil, "", gtserror.NewErrorConflict(errors.New(text), text) + } + // Function to clean up the accepted draft, only called if // creating or updating permission from draft is successful. deleteDraft := func() { @@ -201,11 +211,9 @@ func (p *Processor) DomainPermissionDraftAccept( } } - switch { - - // Easy case, we just need to create a new domain - // permission from the draft, and then delete it. - case existing == nil: + if !existed { + // Easy case, we just need to create a new domain + // permission from the draft, and then delete it. var ( new *apimodel.DomainPermission actionID string @@ -241,11 +249,10 @@ func (p *Processor) DomainPermissionDraftAccept( deleteDraft() return new, actionID, errWithCode - - // Domain permission exists but we should overwrite - // it by just updating the existing domain permission. - // Domain can't change, so no need to re-run side effects. - case overwrite: + } else { + // Domain permission exists but we should overwrite + // it by just updating the existing domain permission. + // Domain can't change, so no need to re-run side effects. existing.SetCreatedByAccountID(permDraft.CreatedByAccountID) existing.SetCreatedByAccount(permDraft.CreatedByAccount) existing.SetPrivateComment(permDraft.PrivateComment) @@ -273,13 +280,6 @@ func (p *Processor) DomainPermissionDraftAccept( apiPerm, errWithCode := p.apiDomainPerm(ctx, existing, false) return apiPerm, "", errWithCode - - // Domain permission exists and we shouldn't - // overwrite it, leave everything alone. - default: - const text = "a domain permission already exists with this permission type and domain" - err := fmt.Errorf("%w: %s", err, text) - return nil, "", gtserror.NewErrorConflict(err, text) } } diff --git a/internal/util/ptr.go b/internal/util/ptr.go index 8a89666c4..d0e835c9e 100644 --- a/internal/util/ptr.go +++ b/internal/util/ptr.go @@ -17,6 +17,8 @@ package util +import "unsafe" + // EqualPtrs returns whether the values contained within two comparable ptr types are equal. func EqualPtrs[T comparable](t1, t2 *T) bool { switch { @@ -59,3 +61,8 @@ func PtrOrValue[T any](t *T, value T) T { } return value } + +func IsNil(i interface{}) bool { + type eface struct{ _, data unsafe.Pointer } + return (*eface)(unsafe.Pointer(&i)).data == nil +} diff --git a/web/source/settings/components/error.tsx b/web/source/settings/components/error.tsx index 977cf06c8..3ca5eb416 100644 --- a/web/source/settings/components/error.tsx +++ b/web/source/settings/components/error.tsx @@ -107,7 +107,11 @@ function Error({ error, reset }: ErrorProps) { { reset && { + e.preventDefault(); + e.stopPropagation(); + reset(); + }} role="button" tabIndex={0} > diff --git a/web/source/settings/components/username.tsx b/web/source/settings/components/username-lozenge.tsx similarity index 51% rename from web/source/settings/components/username.tsx rename to web/source/settings/components/username-lozenge.tsx index 56ba67c4f..9f955cf22 100644 --- a/web/source/settings/components/username.tsx +++ b/web/source/settings/components/username-lozenge.tsx @@ -17,18 +17,107 @@ along with this program. If not, see . */ -import React from "react"; +import React, { useEffect } from "react"; import { useLocation } from "wouter"; import { AdminAccount } from "../lib/types/account"; +import { useLazyGetAccountQuery } from "../lib/query/admin"; +import Loading from "./loading"; +import { Error as ErrorC } from "./error"; -interface UsernameProps { +interface UsernameLozengeProps { + /** + * Either an account ID (for fetching) or an account. + */ + account?: string | AdminAccount; + /** + * Make the lozenge clickable and link to this location. + */ + linkTo?: string; + /** + * Location to set as backLocation after linking to linkTo. + */ + backLocation?: string; + /** + * Additional classnames to add to the lozenge. + */ + classNames?: string[]; +} + +export default function UsernameLozenge({ account, linkTo, backLocation, classNames }: UsernameLozengeProps) { + if (account === undefined) { + return <>[unknown]; + } else if (typeof account === "string") { + return ( + + ); + } else { + return ( + + ); + } + +} + +interface FetchUsernameLozengeProps { + accountID: string; + linkTo?: string; + backLocation?: string; + classNames?: string[]; +} + +function FetchUsernameLozenge({ accountID, linkTo, backLocation, classNames }: FetchUsernameLozengeProps) { + const [ trigger, result ] = useLazyGetAccountQuery(); + + // Call to get the account + // using the provided ID. + useEffect(() => { + trigger(accountID, true); + }, [trigger, accountID]); + + const { + data: account, + isLoading, + isFetching, + isError, + error, + } = result; + + // Wait for the account + // model to be returned. + if (isError) { + return ; + } else if (isLoading || isFetching || account === undefined) { + return ; + } + + return ( + + ); +} + +interface ReadyUsernameLozengeProps { account: AdminAccount; linkTo?: string; backLocation?: string; classNames?: string[]; } -export default function Username({ account, linkTo, backLocation, classNames }: UsernameProps) { +function ReadyUsernameLozenge({ account, linkTo, backLocation, classNames }: ReadyUsernameLozengeProps) { const [ _location, setLocation ] = useLocation(); let className = "username-lozenge"; diff --git a/web/source/settings/lib/query/admin/domain-permissions/drafts.ts b/web/source/settings/lib/query/admin/domain-permissions/drafts.ts index 5d30b4e38..e6160ec5f 100644 --- a/web/source/settings/lib/query/admin/domain-permissions/drafts.ts +++ b/web/source/settings/lib/query/admin/domain-permissions/drafts.ts @@ -26,6 +26,7 @@ import type { DomainPermDraftSearchResp, } from "../../../types/domain-permission"; import parse from "parse-link-header"; +import { PermType } from "../../../types/perm"; const extended = gtsApi.injectEndpoints({ endpoints: (build) => ({ @@ -78,6 +79,63 @@ const extended = gtsApi.injectEndpoints({ }), invalidatesTags: [{ type: "DomainPermissionDraft", id: "TRANSFORMED" }], }), + + acceptDomainPermissionDraft: build.mutation({ + query: ({ id, overwrite }) => ({ + method: "POST", + url: `/api/v1/admin/domain_permission_drafts/${id}/accept`, + asForm: true, + body: { + overwrite: overwrite, + }, + discardEmpty: true + }), + invalidatesTags: (res, _error, { id, permType }) => { + const invalidated: any[] = []; + + // If error, nothing to invalidate. + if (!res) { + return invalidated; + } + + // Invalidate this draft by ID, and + // the transformed list of all drafts. + invalidated.push( + { type: 'DomainPermissionDraft', id: id }, + { type: "DomainPermissionDraft", id: "TRANSFORMED" }, + ); + + // Invalidate cached blocks/allows depending + // on the permType of the accepted draft. + if (permType === "allow") { + invalidated.push("domainAllows"); + } else { + invalidated.push("domainBlocks"); + } + + return invalidated; + } + }), + + removeDomainPermissionDraft: build.mutation({ + query: ({ id, ignore_target }) => ({ + method: "POST", + url: `/api/v1/admin/domain_permission_drafts/${id}/remove`, + asForm: true, + body: { + ignore_target: ignore_target, + }, + discardEmpty: true + }), + invalidatesTags: (res, _error, { id }) => + res + ? [ + { type: "DomainPermissionDraft", id }, + { type: "DomainPermissionDraft", id: "TRANSFORMED" }, + ] + : [], + }) + }), }); @@ -96,8 +154,20 @@ const useGetDomainPermissionDraftQuery = extended.useGetDomainPermissionDraftQue */ const useCreateDomainPermissionDraftMutation = extended.useCreateDomainPermissionDraftMutation; +/** + * Accept a domain permission draft, turning it into an enforced domain permission. + */ +const useAcceptDomainPermissionDraftMutation = extended.useAcceptDomainPermissionDraftMutation; + +/** + * Remove a domain permission draft, optionally ignoring all future drafts targeting the given domain. + */ +const useRemoveDomainPermissionDraftMutation = extended.useRemoveDomainPermissionDraftMutation; + export { useLazySearchDomainPermissionDraftsQuery, useGetDomainPermissionDraftQuery, useCreateDomainPermissionDraftMutation, + useAcceptDomainPermissionDraftMutation, + useRemoveDomainPermissionDraftMutation, }; diff --git a/web/source/settings/lib/util/index.ts b/web/source/settings/lib/util/index.ts index d016f3398..4c8a90626 100644 --- a/web/source/settings/lib/util/index.ts +++ b/web/source/settings/lib/util/index.ts @@ -41,3 +41,16 @@ export function UseOurInstanceAccount(account: AdminAccount): boolean { return !account.domain && account.username == ourDomain; } + +/** + * Uppercase first letter of given string. + */ +export function useCapitalize(i?: string): string { + return useMemo(() => { + if (i === undefined) { + return ""; + } + + return i.charAt(0).toUpperCase() + i.slice(1); + }, [i]); +} diff --git a/web/source/settings/style.css b/web/source/settings/style.css index 2308520df..3c2fe9f73 100644 --- a/web/source/settings/style.css +++ b/web/source/settings/style.css @@ -1364,6 +1364,7 @@ button.tab-button { display: flex; flex-direction: column; flex-wrap: nowrap; + gap: 0.5rem; &.block { border-left: 0.3rem solid $error3; @@ -1385,6 +1386,18 @@ button.tab-button { padding: 0; } } + + .action-buttons { + display: flex; + gap: 0.5rem; + align-items: center; + + > .mutation-button + > button { + font-size: 1rem; + line-height: 1rem; + } + } } } diff --git a/web/source/settings/views/admin/http-header-permissions/detail.tsx b/web/source/settings/views/admin/http-header-permissions/detail.tsx index 522f2dba2..e0d49ffd2 100644 --- a/web/source/settings/views/admin/http-header-permissions/detail.tsx +++ b/web/source/settings/views/admin/http-header-permissions/detail.tsx @@ -17,7 +17,7 @@ along with this program. If not, see . */ -import React, { useEffect, useMemo } from "react"; +import React, { useMemo } from "react"; import { useLocation, useParams } from "wouter"; import { PermType } from "../../../lib/types/perm"; import { useDeleteHeaderAllowMutation, useDeleteHeaderBlockMutation, useGetHeaderAllowQuery, useGetHeaderBlockQuery } from "../../../lib/query/admin/http-header-permissions"; @@ -26,8 +26,7 @@ import { FetchBaseQueryError } from "@reduxjs/toolkit/query"; import { SerializedError } from "@reduxjs/toolkit"; import Loading from "../../../components/loading"; import { Error } from "../../../components/error"; -import { useLazyGetAccountQuery } from "../../../lib/query/admin"; -import Username from "../../../components/username"; +import UsernameLozenge from "../../../components/username-lozenge"; import { useBaseUrl } from "../../../lib/navigation/util"; import BackButton from "../../../components/back-button"; import MutationButton from "../../../components/form/mutation-button"; @@ -92,58 +91,19 @@ interface PermDeetsProps { function PermDeets({ permType, data: perm, - isLoading: isLoadingPerm, - isFetching: isFetchingPerm, - isError: isErrorPerm, - error: errorPerm, + isLoading, + isFetching, + isError, + error, }: PermDeetsProps) { const [ location ] = useLocation(); const baseUrl = useBaseUrl(); - - // Once we've loaded the perm, trigger - // getting the account that created it. - const [ getAccount, getAccountRes ] = useLazyGetAccountQuery(); - useEffect(() => { - if (!perm) { - return; - } - getAccount(perm.created_by, true); - }, [getAccount, perm]); - // Load the createdByAccount if possible, - // returning a username lozenge with - // a link to the account. - const createdByAccount = useMemo(() => { - const { - data: account, - isLoading: isLoadingAccount, - isFetching: isFetchingAccount, - isError: isErrorAccount, - } = getAccountRes; - - // Wait for query to finish, returning - // loading spinner in the meantime. - if (isLoadingAccount || isFetchingAccount || !perm) { - return ; - } else if (isErrorAccount || account === undefined) { - // Fall back to account ID. - return perm?.created_by; - } - - return ( - - ); - }, [getAccountRes, perm, baseUrl, location]); - - // Now wait til the perm itself is loaded. - if (isLoadingPerm || isFetchingPerm) { + // Wait til the perm itself is loaded. + if (isLoading || isFetching) { return ; - } else if (isErrorPerm) { - return ; + } else if (isError) { + return ; } else if (perm === undefined) { throw "perm undefined"; } @@ -172,7 +132,13 @@ function PermDeets({
Created By
-
{createdByAccount}
+
+ +
Header Name
diff --git a/web/source/settings/views/moderation/accounts/pending/index.tsx b/web/source/settings/views/moderation/accounts/pending/index.tsx index f03c4800c..10f7d726a 100644 --- a/web/source/settings/views/moderation/accounts/pending/index.tsx +++ b/web/source/settings/views/moderation/accounts/pending/index.tsx @@ -21,7 +21,7 @@ import React, { ReactNode } from "react"; import { useSearchAccountsQuery } from "../../../../lib/query/admin"; import { PageableList } from "../../../../components/pageable-list"; import { useLocation } from "wouter"; -import Username from "../../../../components/username"; +import UsernameLozenge from "../../../../components/username-lozenge"; import { AdminAccount } from "../../../../lib/types/account"; export default function AccountsPending() { @@ -32,7 +32,7 @@ export default function AccountsPending() { function itemToEntry(account: AdminAccount): ReactNode { const acc = account.account; return ( - . */ -import React, { useEffect, useMemo } from "react"; +import React from "react"; import { useParams } from "wouter"; import Loading from "../../../../components/loading"; import { useBaseUrl } from "../../../../lib/navigation/util"; import BackButton from "../../../../components/back-button"; import { useGetDomainPermissionDraftQuery } from "../../../../lib/query/admin/domain-permissions/drafts"; import { Error as ErrorC } from "../../../../components/error"; -import Username from "../../../../components/username"; -import { useLazyGetAccountQuery } from "../../../../lib/query/admin"; +import UsernameLozenge from "../../../../components/username-lozenge"; export default function DomainPermissionDraftDetail() { const baseUrl = useBaseUrl(); @@ -45,50 +44,6 @@ export default function DomainPermissionDraftDetail() { error, } = useGetDomainPermissionDraftQuery(draftID); - // Once we've triggered loading the perm draft, - // trigger getting the account that created it. - const [ getAccount, getAccountRes ] = useLazyGetAccountQuery(); - useEffect(() => { - if (!permDraft) { - return; - } - - if (!permDraft.created_by) { - return; - } - - getAccount(permDraft.created_by, true); - }, [getAccount, permDraft]); - - // Load the createdByAccount if possible, - // returning a username lozenge with - // a link to the account. - const createdByAccount = useMemo(() => { - const { - data: account, - isLoading: isLoadingAccount, - isFetching: isFetchingAccount, - isError: isErrorAccount, - } = getAccountRes; - - // Wait for query to finish, returning - // loading spinner in the meantime. - if (isLoadingAccount || isFetchingAccount || !permDraft) { - return ; - } else if (isErrorAccount || account === undefined) { - // Fall back to account ID. - return permDraft?.created_by; - } - - return ( - - ); - }, [getAccountRes, permDraft]); - if (isLoading || isFetching) { return ; } else if (isError) { @@ -117,7 +72,13 @@ export default function DomainPermissionDraftDetail() {
Created By
-
{createdByAccount}
+
+ +
Domain
diff --git a/web/source/settings/views/moderation/domain-permissions/drafts/index.tsx b/web/source/settings/views/moderation/domain-permissions/drafts/index.tsx index 1a9820cc5..76aff62d8 100644 --- a/web/source/settings/views/moderation/domain-permissions/drafts/index.tsx +++ b/web/source/settings/views/moderation/domain-permissions/drafts/index.tsx @@ -23,11 +23,12 @@ import { useTextInput } from "../../../../lib/form"; import { PageableList } from "../../../../components/pageable-list"; import MutationButton from "../../../../components/form/mutation-button"; import { useLocation, useSearch } from "wouter"; -import { useLazySearchDomainPermissionDraftsQuery } from "../../../../lib/query/admin/domain-permissions/drafts"; +import { useAcceptDomainPermissionDraftMutation, useLazySearchDomainPermissionDraftsQuery, useRemoveDomainPermissionDraftMutation } from "../../../../lib/query/admin/domain-permissions/drafts"; import { DomainPerm } from "../../../../lib/types/domain-permission"; import { Error as ErrorC } from "../../../../components/error"; import { Select, TextInput } from "../../../../components/form/inputs"; import { formDomainValidator } from "../../../../lib/util/formvalidators"; +import { useCapitalize } from "../../../../lib/util"; export default function DomainPermissionDraftsSearch() { return ( @@ -190,20 +191,31 @@ interface DraftEntryProps { function DraftListEntry({ permDraft, linkTo, backLocation }: DraftEntryProps) { const [ _location, setLocation ] = useLocation(); + const [ accept, acceptResult ] = useAcceptDomainPermissionDraftMutation(); + const [ remove, removeResult ] = useRemoveDomainPermissionDraftMutation(); const domain = permDraft.domain; const permType = permDraft.permission_type; + const permTypeUpper = useCapitalize(permType); if (!permType) { return ; } + + const publicComment = permDraft.public_comment ?? "[none]"; const privateComment = permDraft.private_comment ?? "[none]"; const subscriptionID = permDraft.subscription_id ?? "[none]"; + const id = permDraft.id; + if (!id) { + return ; + } + + const title = `${permTypeUpper} ${domain}`; return ( { // When clicking on a draft, direct // to the detail view for that draft. @@ -217,6 +229,7 @@ function DraftListEntry({ permDraft, linkTo, backLocation }: DraftEntryProps) { role="link" tabIndex={0} > +

{title}

Domain:
@@ -236,11 +249,45 @@ function DraftListEntry({ permDraft, linkTo, backLocation }: DraftEntryProps) {
Private comment:
{privateComment}
+
+
Public comment:
+
{publicComment}
+
Subscription:
{subscriptionID}
+
+ { + e.preventDefault(); + e.stopPropagation(); + accept({ id, permType }); + }} + disabled={false} + showError={true} + result={acceptResult} + /> + { + e.preventDefault(); + e.stopPropagation(); + remove({ id }); + }} + disabled={false} + showError={true} + result={removeResult} + /> +
); } diff --git a/web/source/settings/views/moderation/domain-permissions/drafts/new.tsx b/web/source/settings/views/moderation/domain-permissions/drafts/new.tsx index 90c46076f..6da043c70 100644 --- a/web/source/settings/views/moderation/domain-permissions/drafts/new.tsx +++ b/web/source/settings/views/moderation/domain-permissions/drafts/new.tsx @@ -24,8 +24,11 @@ import { useBoolInput, useRadioInput, useTextInput } from "../../../../lib/form" import { formDomainValidator } from "../../../../lib/util/formvalidators"; import MutationButton from "../../../../components/form/mutation-button"; import { Checkbox, RadioGroup, TextArea, TextInput } from "../../../../components/form/inputs"; +import { useLocation } from "wouter"; export default function DomainPermissionDraftNew() { + const [ _location, setLocation ] = useLocation(); + const form = { domain: useTextInput("domain", { validator: formDomainValidator, @@ -40,9 +43,21 @@ export default function DomainPermissionDraftNew() { public_comment: useTextInput("public_comment"), private_comment: useTextInput("private_comment"), }; - - const [formSubmit, result] = useFormSubmit(form, useCreateDomainPermissionDraftMutation()); - + + const [formSubmit, result] = useFormSubmit( + form, + useCreateDomainPermissionDraftMutation(), + { + changedOnly: false, + onFinish: (res) => { + if (res.data) { + // Creation successful, + // redirect to drafts overview. + setLocation(`/drafts/search`); + } + }, + }); + return (
Reported account
-
Reported by
-
Handled by
-
Reported account:
- @@ -216,7 +216,7 @@ function ReportListEntry({ report, linkTo, backLocation }: ReportEntryProps) {
Reported by:
- +