From e9bb7ddd3aa11da5c48a75c4a600f8fe5cc1c990 Mon Sep 17 00:00:00 2001 From: tobi <31960611+tsmethurst@users.noreply.github.com> Date: Sun, 5 Jan 2025 13:20:33 +0100 Subject: [feature] Create/update/remove domain permission subscriptions (#3623) * [feature] Create/update/remove domain permission subscriptions * lint * envparsing * remove errant fmt.Println * create drafts, subs, exclude, from snapshot models * name etag column correctly * remove count column * lint --- .../domain-permissions/subscriptions/common.tsx | 181 ++++++++++ .../domain-permissions/subscriptions/detail.tsx | 384 +++++++++++++++++++++ .../domain-permissions/subscriptions/index.tsx | 170 +++++++++ .../domain-permissions/subscriptions/new.tsx | 230 ++++++++++++ .../domain-permissions/subscriptions/preview.tsx | 100 ++++++ web/source/settings/views/moderation/menu.tsx | 22 ++ web/source/settings/views/moderation/router.tsx | 8 + 7 files changed, 1095 insertions(+) create mode 100644 web/source/settings/views/moderation/domain-permissions/subscriptions/common.tsx create mode 100644 web/source/settings/views/moderation/domain-permissions/subscriptions/detail.tsx create mode 100644 web/source/settings/views/moderation/domain-permissions/subscriptions/index.tsx create mode 100644 web/source/settings/views/moderation/domain-permissions/subscriptions/new.tsx create mode 100644 web/source/settings/views/moderation/domain-permissions/subscriptions/preview.tsx (limited to 'web/source/settings/views') diff --git a/web/source/settings/views/moderation/domain-permissions/subscriptions/common.tsx b/web/source/settings/views/moderation/domain-permissions/subscriptions/common.tsx new file mode 100644 index 000000000..8668caa4b --- /dev/null +++ b/web/source/settings/views/moderation/domain-permissions/subscriptions/common.tsx @@ -0,0 +1,181 @@ +/* + GoToSocial + Copyright (C) GoToSocial Authors admin@gotosocial.org + SPDX-License-Identifier: AGPL-3.0-or-later + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +import React, { useMemo } from "react"; +import { useLocation } from "wouter"; +import { DomainPermSub } from "../../../../lib/types/domain-permission"; +import { yesOrNo } from "../../../../lib/util"; + +export function DomainPermissionSubscriptionHelpText() { + return ( + <> + Domain permission subscriptions allow your instance to "subscribe" to a list of block or allows at a given url. +
+ Every 24 hours, each subscribed list is fetched by your instance, and any discovered + permissions in each list are loaded into your instance as blocks/allows/drafts. + + ); +} + +export function DomainPermissionSubscriptionDocsLink() { + return ( + + Learn more about domain permission subscriptions (opens in a new tab) + + ); +} + +export interface SubscriptionEntryProps { + permSub: DomainPermSub; + linkTo: string; + backLocation: string; +} + +export function SubscriptionListEntry({ permSub, linkTo, backLocation }: SubscriptionEntryProps) { + const [ _location, setLocation ] = useLocation(); + + const permType = permSub.permission_type; + if (!permType) { + throw "permission_type was undefined"; + } + + const { + priority, + title, + uri, + as_draft: asDraft, + adopt_orphans: adoptOrphans, + content_type: contentType, + fetched_at: fetchedAt, + successfully_fetched_at: successfullyFetchedAt, + count, + } = permSub; + + const ariaLabel = useMemo(() => { + let ariaLabel = ""; + + // Prepend title. + if (title.length !== 0) { + ariaLabel += `${title}, create `; + } else { + ariaLabel += "Create "; + } + + // Add perm type. + ariaLabel += permType; + + // Alter wording + // if using drafts. + if (asDraft) { + ariaLabel += " drafts from "; + } else { + ariaLabel += "s from "; + } + + // Add url. + ariaLabel += uri; + + return ariaLabel; + }, [title, permType, asDraft, uri]); + + let fetchedAtStr = "never"; + if (fetchedAt) { + fetchedAtStr = new Date(fetchedAt).toDateString(); + } + + let successfullyFetchedAtStr = "never"; + if (successfullyFetchedAt) { + successfullyFetchedAtStr = new Date(successfullyFetchedAt).toDateString(); + } + + return ( + { + // When clicking on a subscription, direct + // to the detail view for that subscription. + setLocation(linkTo, { + // Store the back location in history so + // the detail view can use it to return to + // this page (including query parameters). + state: { backLocation: backLocation } + }); + }} + role="link" + tabIndex={0} + > +
+ { permSub.title !== "" && + + {title} + + } +
+
Priority:
+
{priority}
+
+
+
Permission type:
+
+ + {permType} +
+
+
+
URL:
+
{uri}
+
+
+
Content type:
+
{contentType}
+
+
+
Create as draft:
+
{yesOrNo(asDraft)}
+
+
+
Adopt orphans:
+
{yesOrNo(adoptOrphans)}
+
+
+
Last fetch attempt:
+
{fetchedAtStr}
+
+
+
Last successful fetch:
+
{successfullyFetchedAtStr}
+
+
+
Discovered {permType}s:
+
{count}
+
+
+
+ ); +} diff --git a/web/source/settings/views/moderation/domain-permissions/subscriptions/detail.tsx b/web/source/settings/views/moderation/domain-permissions/subscriptions/detail.tsx new file mode 100644 index 000000000..408d81b92 --- /dev/null +++ b/web/source/settings/views/moderation/domain-permissions/subscriptions/detail.tsx @@ -0,0 +1,384 @@ +/* + GoToSocial + Copyright (C) GoToSocial Authors admin@gotosocial.org + SPDX-License-Identifier: AGPL-3.0-or-later + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +import React, { useState } from "react"; +import { useLocation, useParams } from "wouter"; +import { useBaseUrl } from "../../../../lib/navigation/util"; +import BackButton from "../../../../components/back-button"; +import { useGetDomainPermissionSubscriptionQuery, useRemoveDomainPermissionSubscriptionMutation, useUpdateDomainPermissionSubscriptionMutation } from "../../../../lib/query/admin/domain-permissions/subscriptions"; +import { useBoolInput, useNumberInput, useTextInput } from "../../../../lib/form"; +import FormWithData from "../../../../lib/form/form-with-data"; +import { DomainPermSub } from "../../../../lib/types/domain-permission"; +import MutationButton from "../../../../components/form/mutation-button"; +import { Checkbox, NumberInput, Select, TextInput } from "../../../../components/form/inputs"; +import useFormSubmit from "../../../../lib/form/submit"; +import UsernameLozenge from "../../../../components/username-lozenge"; +import { urlValidator } from "../../../../lib/util/formvalidators"; + +export default function DomainPermissionSubscriptionDetail() { + const params = useParams(); + let id = params.permSubId as string | undefined; + if (!id) { + throw "no permSub ID"; + } + + return ( + + ); +} + +function DomainPermSubForm({ data: permSub }: { data: DomainPermSub }) { + const baseUrl = useBaseUrl(); + const backLocation: string = history.state?.backLocation ?? `~${baseUrl}/subscriptions/search`; + + return ( +
+

Domain Permission Subscription Detail

+ + + +
+ ); +} + +function DomainPermSubDetails({ permSub }: { permSub: DomainPermSub }) { + const [ location ] = useLocation(); + const baseUrl = useBaseUrl(); + + const permType = permSub.permission_type; + if (!permType) { + throw "permission_type was undefined"; + } + + const created = new Date(permSub.created_at).toDateString(); + let fetchedAtStr = "never"; + if (permSub.fetched_at) { + fetchedAtStr = new Date(permSub.fetched_at).toDateString(); + } + + let successfullyFetchedAtStr = "never"; + if (permSub.successfully_fetched_at) { + successfullyFetchedAtStr = new Date(permSub.successfully_fetched_at).toDateString(); + } + + return ( +
+
+
Permission type:
+
+ + {permType} +
+
+
+
ID
+
{permSub.id}
+
+
+
Created
+
+
+
+
Created By
+
+ +
+
+
+
Last fetch attempt:
+
{fetchedAtStr}
+
+
+
Last successful fetch:
+
{successfullyFetchedAtStr}
+
+
+
Discovered {permSub.permission_type}s:
+
{permSub.count}
+
+
+ ); +} + +function UpdateDomainPermSub({ permSub }: { permSub: DomainPermSub }) { + const [ showPassword, setShowPassword ] = useState(false); + const form = { + priority: useNumberInput("priority", { source: permSub }), + uri: useTextInput("uri", { + source: permSub, + validator: urlValidator, + }), + content_type: useTextInput("content_type", { source: permSub }), + title: useTextInput("title", { source: permSub }), + as_draft: useBoolInput("as_draft", { source: permSub }), + adopt_orphans: useBoolInput("adopt_orphans", { source: permSub }), + useBasicAuth: useBoolInput("useBasicAuth", { + defaultValue: + (permSub.fetch_password !== undefined && permSub.fetch_password !== "") || + (permSub.fetch_username !== undefined && permSub.fetch_username !== ""), + nosubmit: true + }), + fetch_username: useTextInput("fetch_username", { + source: permSub + }), + fetch_password: useTextInput("fetch_password", { + source: permSub + }), + }; + + const [submitUpdate, updateResult] = useFormSubmit( + form, + useUpdateDomainPermissionSubscriptionMutation(), + { + changedOnly: true, + customizeMutationArgs: (mutationData) => { + // Clear username + password if they were set, + // but user has selected to not use basic auth. + if (!form.useBasicAuth.value) { + if (permSub.fetch_username !== undefined && permSub.fetch_username !== "") { + mutationData["fetch_username"] = ""; + } + if (permSub.fetch_password !== undefined && permSub.fetch_password !== "") { + mutationData["fetch_password"] = ""; + } + } + + // Remove useBasicAuth if included. + delete mutationData["useBasicAuth"]; + + // Modify mutation argument to + // include ID and permission type. + return { + id: permSub.id, + permType: permSub.permission_type, + formData: mutationData, + }; + }, + onFinish: res => { + // On a successful response that returns data, + // clear the fetch_username and fetch_password + // fields if they weren't set on the returned sub. + if (res.data) { + if (res.data.fetch_username === undefined || res.data.fetch_username === "") { + form.fetch_username.setter(""); + } + if (res.data.fetch_password === undefined || res.data.fetch_password === "") { + form.fetch_password.setter(""); + } + } + } + } + ); + + const submitDisabled = () => { + // If no basic auth, we don't care what + // fetch_password and fetch_username are. + if (!form.useBasicAuth.value) { + return false; + } + + // Either of fetch_password or fetch_username must be set. + return !(form.fetch_password.value || form.fetch_username.value); + }; + + return ( +
+

Edit Subscription

+ + + + + + + + + + + + } + > + + + + No subscriptions found that match your query.} + prevNextLinks={searchRes.data?.links} + /> + + ); +} diff --git a/web/source/settings/views/moderation/domain-permissions/subscriptions/new.tsx b/web/source/settings/views/moderation/domain-permissions/subscriptions/new.tsx new file mode 100644 index 000000000..e29e3d755 --- /dev/null +++ b/web/source/settings/views/moderation/domain-permissions/subscriptions/new.tsx @@ -0,0 +1,230 @@ +/* + GoToSocial + Copyright (C) GoToSocial Authors admin@gotosocial.org + SPDX-License-Identifier: AGPL-3.0-or-later + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +import React, { useState } from "react"; +import useFormSubmit from "../../../../lib/form/submit"; +import { useCreateDomainPermissionSubscriptionMutation } from "../../../../lib/query/admin/domain-permissions/subscriptions"; +import { useBoolInput, useNumberInput, useTextInput } from "../../../../lib/form"; +import { urlValidator } from "../../../../lib/util/formvalidators"; +import MutationButton from "../../../../components/form/mutation-button"; +import { Checkbox, NumberInput, Select, TextInput } from "../../../../components/form/inputs"; +import { useLocation } from "wouter"; +import { DomainPermissionSubscriptionDocsLink, DomainPermissionSubscriptionHelpText } from "./common"; + +export default function DomainPermissionSubscriptionNew() { + const [ _location, setLocation ] = useLocation(); + + const useBasicAuth = useBoolInput("useBasicAuth", { defaultValue: false }); + const form = { + priority: useNumberInput("priority", { defaultValue: 0 }), + uri: useTextInput("uri", { + validator: urlValidator, + }), + content_type: useTextInput("content_type", { defaultValue: "text/csv" }), + permission_type: useTextInput("permission_type", { defaultValue: "block" }), + title: useTextInput("title"), + as_draft: useBoolInput("as_draft", { defaultValue: true }), + adopt_orphans: useBoolInput("adopt_orphans", { defaultValue: false }), + fetch_username: useTextInput("fetch_username", { + nosubmit: !useBasicAuth.value + }), + fetch_password: useTextInput("fetch_password", { + nosubmit: !useBasicAuth.value + }), + }; + + const [ showPassword, setShowPassword ] = useState(false); + + const [formSubmit, result] = useFormSubmit( + form, + useCreateDomainPermissionSubscriptionMutation(), + { + changedOnly: false, + onFinish: (res) => { + if (res.data) { + // Creation successful, + // redirect to subscription detail. + setLocation(`/subscriptions/${res.data.id}`); + } + }, + }); + + const submitDisabled = () => { + // URI required. + if (!form.uri.value || !form.uri.valid) { + return true; + } + + // If no basic auth, we don't care what + // fetch_password and fetch_username are. + if (!useBasicAuth.value) { + return false; + } + + // Either of fetch_password or fetch_username must be set. + return !(form.fetch_password.value || form.fetch_username.value); + }; + + return ( +
+
+

New Domain Permission Subscription

+

+ +
+ + + + + + + + + + + } + /> + + + <>Use + basic auth + <> when fetching + + } + field={useBasicAuth} + /> + + { useBasicAuth.value && + <> + +
+ + +
+ + } + + + + + + { !form.as_draft.value && +
+ + + Unchecking "create permissions as drafts" means that permissions found on the + subscribed list will be enforced immediately the next time the list is fetched. +
+ If you're subscribing to a block list, this means that blocks will be created + automatically from the given list, potentially severing any existing follow + relationships with accounts on the blocked domain. +
+ Before saving, make sure this is what you really want to do, and consider + creating domain excludes for domains that you want to manage manually. +
+
+ } + + + + ); +} diff --git a/web/source/settings/views/moderation/domain-permissions/subscriptions/preview.tsx b/web/source/settings/views/moderation/domain-permissions/subscriptions/preview.tsx new file mode 100644 index 000000000..a23c18c9e --- /dev/null +++ b/web/source/settings/views/moderation/domain-permissions/subscriptions/preview.tsx @@ -0,0 +1,100 @@ +/* + GoToSocial + Copyright (C) GoToSocial Authors admin@gotosocial.org + SPDX-License-Identifier: AGPL-3.0-or-later + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +import React, { ReactNode } from "react"; + +import { useTextInput } from "../../../../lib/form"; +import { PageableList } from "../../../../components/pageable-list"; +import { useLocation } from "wouter"; +import { useGetDomainPermissionSubscriptionsPreviewQuery } from "../../../../lib/query/admin/domain-permissions/subscriptions"; +import { DomainPermSub } from "../../../../lib/types/domain-permission"; +import { Select } from "../../../../components/form/inputs"; +import { DomainPermissionSubscriptionDocsLink, SubscriptionListEntry } from "./common"; +import { PermType } from "../../../../lib/types/perm"; + +export default function DomainPermissionSubscriptionsPreview() { + return ( +
+
+

Domain Permission Subscriptions Preview

+

+ You can use the form below to view through domain permission subscriptions sorted by priority (high to low). +
+ This reflects the order in which they will actually be fetched by your instance, with higher-priority subscriptions + creating permissions first, followed by lower-priority subscriptions. +

+ +
+ +
+ ); +} + +function DomainPermissionSubscriptionsPreviewForm() { + const [ location, _setLocation ] = useLocation(); + + const permType = useTextInput("permission_type", { defaultValue: "block" }); + const { + data: permSubs, + isLoading, + isFetching, + isSuccess, + isError, + error, + } = useGetDomainPermissionSubscriptionsPreviewQuery(permType.value as PermType); + + // Function to map an item to a list entry. + function itemToEntry(permSub: DomainPermSub): ReactNode { + return ( + + ); + } + + return ( + <> +
+ +
+ No {permType.value}list subscriptions found.} + /> + + ); +} diff --git a/web/source/settings/views/moderation/menu.tsx b/web/source/settings/views/moderation/menu.tsx index 7ac6f9327..17b2f18e0 100644 --- a/web/source/settings/views/moderation/menu.tsx +++ b/web/source/settings/views/moderation/menu.tsx @@ -150,6 +150,28 @@ function ModerationDomainPermsMenu() { icon="fa-plus" /> + + + + + ); } diff --git a/web/source/settings/views/moderation/router.tsx b/web/source/settings/views/moderation/router.tsx index 779498ffe..90214188f 100644 --- a/web/source/settings/views/moderation/router.tsx +++ b/web/source/settings/views/moderation/router.tsx @@ -35,6 +35,10 @@ import DomainPermissionDraftDetail from "./domain-permissions/drafts/detail"; import DomainPermissionExcludeDetail from "./domain-permissions/excludes/detail"; import DomainPermissionExcludesSearch from "./domain-permissions/excludes"; import DomainPermissionExcludeNew from "./domain-permissions/excludes/new"; +import DomainPermissionSubscriptionsSearch from "./domain-permissions/subscriptions"; +import DomainPermissionSubscriptionNew from "./domain-permissions/subscriptions/new"; +import DomainPermissionSubscriptionDetail from "./domain-permissions/subscriptions/detail"; +import DomainPermissionSubscriptionsPreview from "./domain-permissions/subscriptions/preview"; /* EXPORTED COMPONENTS @@ -151,6 +155,10 @@ function ModerationDomainPermsRouter() { + + + + -- cgit v1.3