From 0fc158a7d0593f8a2d4d952708103c96dc634120 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Tue, 2 Apr 2024 11:45:15 +0200 Subject: [PATCH 1/5] Allow to manage instance relays MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- src/api/hooks/admin/index.ts | 3 + src/api/hooks/admin/useCreateRelay.ts | 18 +++ src/api/hooks/admin/useDeleteDomain.ts | 7 +- src/api/hooks/admin/useDeleteRelay.ts | 19 +++ src/api/hooks/admin/useRelays.ts | 25 ++++ src/entity-store/entities.ts | 2 + src/features/admin/domains.tsx | 4 +- src/features/admin/relays.tsx | 141 ++++++++++++++++++ .../components/new-folder-form.tsx | 2 +- src/features/ui/index.tsx | 2 + src/features/ui/util/async-components.ts | 1 + src/locales/en.json | 10 ++ src/schemas/index.ts | 1 + src/schemas/relay.ts | 13 ++ 14 files changed, 239 insertions(+), 9 deletions(-) create mode 100644 src/api/hooks/admin/useCreateRelay.ts create mode 100644 src/api/hooks/admin/useDeleteRelay.ts create mode 100644 src/api/hooks/admin/useRelays.ts create mode 100644 src/features/admin/relays.tsx create mode 100644 src/schemas/relay.ts diff --git a/src/api/hooks/admin/index.ts b/src/api/hooks/admin/index.ts index 23f815c8a..49e22cf86 100644 --- a/src/api/hooks/admin/index.ts +++ b/src/api/hooks/admin/index.ts @@ -1,6 +1,9 @@ export { useCreateDomain, type CreateDomainParams } from './useCreateDomain'; +export { useCreateRelay } from './useCreateRelay'; export { useDeleteDomain } from './useDeleteDomain'; +export { useDeleteRelay } from './useDeleteRelay'; export { useDomains } from './useDomains'; +export { useRelays } from './useRelays'; export { useSuggest } from './useSuggest'; export { useUpdateDomain } from './useUpdateDomain'; export { useVerify } from './useVerify'; \ No newline at end of file diff --git a/src/api/hooks/admin/useCreateRelay.ts b/src/api/hooks/admin/useCreateRelay.ts new file mode 100644 index 000000000..d29f15a99 --- /dev/null +++ b/src/api/hooks/admin/useCreateRelay.ts @@ -0,0 +1,18 @@ +import { Entities } from 'soapbox/entity-store/entities'; +import { useCreateEntity } from 'soapbox/entity-store/hooks'; +import { useApi } from 'soapbox/hooks'; +import { relaySchema } from 'soapbox/schemas'; + +const useCreateRelay = () => { + const api = useApi(); + + const { createEntity, ...rest } = useCreateEntity([Entities.RELAYS], (relayUrl: string) => + api.post('/api/v1/pleroma/admin/relay', { relay_url: relayUrl }), { schema: relaySchema }); + + return { + createRelay: createEntity, + ...rest, + }; +}; + +export { useCreateRelay }; diff --git a/src/api/hooks/admin/useDeleteDomain.ts b/src/api/hooks/admin/useDeleteDomain.ts index 3975209e0..05aeb70e8 100644 --- a/src/api/hooks/admin/useDeleteDomain.ts +++ b/src/api/hooks/admin/useDeleteDomain.ts @@ -2,11 +2,6 @@ import { Entities } from 'soapbox/entity-store/entities'; import { useDeleteEntity } from 'soapbox/entity-store/hooks'; import { useApi } from 'soapbox/hooks'; -interface DeleteDomainParams { - domain: string; - public: boolean; -} - const useDeleteDomain = () => { const api = useApi(); @@ -23,4 +18,4 @@ const useDeleteDomain = () => { }; }; -export { useDeleteDomain, type DeleteDomainParams }; +export { useDeleteDomain }; diff --git a/src/api/hooks/admin/useDeleteRelay.ts b/src/api/hooks/admin/useDeleteRelay.ts new file mode 100644 index 000000000..b7f284025 --- /dev/null +++ b/src/api/hooks/admin/useDeleteRelay.ts @@ -0,0 +1,19 @@ +import { Entities } from 'soapbox/entity-store/entities'; +import { useDeleteEntity } from 'soapbox/entity-store/hooks'; +import { useApi } from 'soapbox/hooks'; + +const useDeleteRelay = () => { + const api = useApi(); + + const { deleteEntity, ...rest } = useDeleteEntity(Entities.RELAYS, (relayUrl: string) => + api.delete('/api/v1/pleroma/admin/relay', { + data: { relay_url: relayUrl }, + })); + + return { + mutate: deleteEntity, + ...rest, + }; +}; + +export { useDeleteRelay }; diff --git a/src/api/hooks/admin/useRelays.ts b/src/api/hooks/admin/useRelays.ts new file mode 100644 index 000000000..b6a0b1e26 --- /dev/null +++ b/src/api/hooks/admin/useRelays.ts @@ -0,0 +1,25 @@ +import { useQuery } from '@tanstack/react-query'; + +import { useApi } from 'soapbox/hooks'; +import { relaySchema, type Relay } from 'soapbox/schemas'; + +const useRelays = () => { + const api = useApi(); + + const getRelays = async () => { + const { data } = await api.get<{ relays: Relay[] }>('/api/v1/pleroma/admin/relay'); + + const normalizedData = data.relays?.map((relay) => relaySchema.parse(relay)); + return normalizedData; + }; + + const result = useQuery>({ + queryKey: ['relays'], + queryFn: getRelays, + placeholderData: [], + }); + + return result; +}; + +export { useRelays }; diff --git a/src/entity-store/entities.ts b/src/entity-store/entities.ts index 97ef4d9ac..ea7433104 100644 --- a/src/entity-store/entities.ts +++ b/src/entity-store/entities.ts @@ -11,6 +11,7 @@ enum Entities { GROUP_TAGS = 'GroupTags', PATRON_USERS = 'PatronUsers', RELATIONSHIPS = 'Relationships', + RELAYS = 'Relays', STATUSES = 'Statuses' } @@ -24,6 +25,7 @@ interface EntityTypes { [Entities.GROUP_TAGS]: Schemas.GroupTag; [Entities.PATRON_USERS]: Schemas.PatronUser; [Entities.RELATIONSHIPS]: Schemas.Relationship; + [Entities.RELAYS]: Schemas.Relay; [Entities.STATUSES]: Schemas.Status; } diff --git a/src/features/admin/domains.tsx b/src/features/admin/domains.tsx index 024cf750c..1b7aef18f 100644 --- a/src/features/admin/domains.tsx +++ b/src/features/admin/domains.tsx @@ -37,7 +37,7 @@ const Domain: React.FC = ({ domain }) => { dispatch(openModal('EDIT_DOMAIN', { domainId: domain.id })); }; - const handleDeleteDomain = (id: string) => () => { + const handleDeleteDomain = () => () => { dispatch(openModal('CONFIRM', { heading: intl.formatMessage(messages.deleteHeading), message: intl.formatMessage(messages.deleteMessage), @@ -90,7 +90,7 @@ const Domain: React.FC = ({ domain }) => { - diff --git a/src/features/admin/relays.tsx b/src/features/admin/relays.tsx new file mode 100644 index 000000000..11645c1ba --- /dev/null +++ b/src/features/admin/relays.tsx @@ -0,0 +1,141 @@ +import React from 'react'; +import { FormattedMessage, defineMessages, useIntl } from 'react-intl'; + +import { useCreateRelay, useDeleteRelay, useRelays } from 'soapbox/api/hooks/admin'; +import ScrollableList from 'soapbox/components/scrollable-list'; +import { Button, Column, Form, HStack, Input, Stack, Text } from 'soapbox/components/ui'; +import { useTextField } from 'soapbox/hooks/forms'; +import toast from 'soapbox/toast'; + +import type { Relay as RelayEntity } from 'soapbox/schemas'; + +const messages = defineMessages({ + heading: { id: 'column.admin.relays', defaultMessage: 'Instance relays' }, + relayDeleteSuccess: { id: 'admin.relays.deleted', defaultMessage: 'Relay unfollowed' }, + label: { id: 'admin.relays.new.url_placeholder', defaultMessage: 'Instance relay URL' }, + createSuccess: { id: 'admin.relays.add.success', defaultMessage: 'Instance relay followed' }, + createFail: { id: 'admin.relays.add.fail', defaultMessage: 'Failed to follow the instance relay' }, +}); + +interface IRelay { + relay: RelayEntity; +} + +const Relay: React.FC = ({ relay }) => { + const { mutate: deleteRelay } = useDeleteRelay(); + const { refetch } = useRelays(); + + const handleDeleteRelay = () => () => { + deleteRelay(relay.actor).then(() => { + refetch(); + toast.success(messages.relayDeleteSuccess); + }).catch(() => {}); + }; + + return ( +
+ + + + + + + {' '} + {relay.actor} + + {relay.followed_back && ( + + + + )} + + + + + +
+ ); +}; + +const NewRelayForm: React.FC = () => { + const intl = useIntl(); + + const name = useTextField(); + + const { createRelay, isSubmitting } = useCreateRelay(); + const { refetch } = useRelays(); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + createRelay(name.value, { + onSuccess() { + toast.success(messages.createSuccess); + refetch(); + }, + onError() { + toast.success(messages.createFail); + }, + }); + }; + + const label = intl.formatMessage(messages.label); + + return ( +
+ + + + + +
+ ); +}; + +const Relays: React.FC = () => { + const intl = useIntl(); + + const { data: relays, isFetching } = useRelays(); + + const emptyMessage = ; + + return ( + + + + + {relays && ( + + {relays.map((relay) => ( + + ))} + + )} + + + ); +}; + +export default Relays; diff --git a/src/features/bookmark-folders/components/new-folder-form.tsx b/src/features/bookmark-folders/components/new-folder-form.tsx index 3244cbaea..badd6006f 100644 --- a/src/features/bookmark-folders/components/new-folder-form.tsx +++ b/src/features/bookmark-folders/components/new-folder-form.tsx @@ -37,7 +37,7 @@ const NewFolderForm: React.FC = () => { return (
- +