Skip to content

Commit

Permalink
[feature] Allow import/export/creation of domain allows via admin pan…
Browse files Browse the repository at this point in the history
…el (#2264)

* it's happening!

* aaa

* fix silly whoopsie

* it's working pa! it's working ma!

* model report parameters

* shuffle some more stuff around

* getting there

* oo hoo

* finish tidying up for now

* aaa

* fix use form submit errors

* peepee poo poo

* aaaaa

* ffff

* they see me typin', they hatin'

* boop

* aaa

* oooo

* typing typing tappa tappa

* almost done typing

* weee

* alright

* push it push it real good doo doo doo doo doo doo

* thingy no worky

* almost done

* mutation modifers not quite right

* hmm

* it works

* view blocks + allows nicely

* it works!

* typia install

* the old linterino

* linter plz
  • Loading branch information
tsmethurst authored Oct 17, 2023
1 parent 48725f7 commit 637f188
Show file tree
Hide file tree
Showing 77 changed files with 4,154 additions and 1,690 deletions.
3 changes: 2 additions & 1 deletion .drone.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ steps:
path: /tmp/cache
commands:
- yarn --cwd ./web/source install --frozen-lockfile --cache-folder /tmp/cache
- yarn --cwd ./web/source ts-patch install # https://typia.io/docs/setup/#manual-setup

- name: web-lint
image: node:18-alpine
Expand Down Expand Up @@ -191,6 +192,6 @@ steps:

---
kind: signature
hmac: c3efbd528a76016562f88ae435141cfb5fd6d4d07b6ad2a24ecc23cb529cc1c6
hmac: d7b93470276a0df7e4d862941489f00da107df3d085200009b776d33599e6043

...
1 change: 1 addition & 0 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ before:
- sed -i "s/REPLACE_ME/{{ incpatch .Version }}/" web/assets/swagger.yaml
# Install web deps + bundle web assets
- yarn --cwd ./web/source install
- yarn --cwd ./web/source ts-patch install # https://typia.io/docs/setup/#manual-setup
- yarn --cwd ./web/source build
builds:
# https://goreleaser.com/customization/build/
Expand Down
6 changes: 4 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,13 +229,15 @@ Using [NVM](https://github.com/nvm-sh/nvm) is one convenient way to install them
To install frontend dependencies:

```bash
yarn --cwd web/source
yarn --cwd ./web/source install && yarn --cwd ./web/source ts-patch install
```

The `ts-patch` step is necessary because of Typia, which we use for some type validation: see [Typia install docs](https://typia.io/docs/setup/#manual-setup).

To recompile frontend bundles into `web/assets/dist`:

```bash
yarn --cwd web/source build
yarn --cwd ./web/source build
```

#### Live Reloading
Expand Down
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ FROM --platform=${BUILDPLATFORM} node:18-alpine AS bundler

COPY web web
RUN yarn --cwd ./web/source install && \
yarn --cwd ./web/source ts-patch install && \
yarn --cwd ./web/source build && \
rm -rf ./web/source

Expand Down
2 changes: 1 addition & 1 deletion internal/api/client/admin/domainpermission.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ func (m *Module) createDomainPermissions(

if importing && form.Domains.Size == 0 {
err = errors.New("import was specified but list of domains is empty")
} else if form.Domain == "" {
} else if !importing && form.Domain == "" {
err = errors.New("empty domain provided")
}

Expand Down
9 changes: 8 additions & 1 deletion web/source/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@
"@browserify/envify": "^6.0.0",
"@browserify/uglifyify": "^6.0.0",
"@joepie91/eslint-config": "^1.1.1",
"@types/bluebird": "^3.5.39",
"@types/is-valid-domain": "^0.0.2",
"@types/papaparse": "^5.3.9",
"@types/psl": "^1.1.1",
"@types/react-dom": "^18.2.8",
"@typescript-eslint/eslint-plugin": "^6.7.4",
"@typescript-eslint/parser": "^6.7.4",
Expand All @@ -63,7 +67,10 @@
"postcss-nested": "^6.0.0",
"source-map-loader": "^4.0.1",
"ts-loader": "^9.4.4",
"ts-node": "^10.9.1",
"ts-patch": "^3.0.2",
"tsify": "^5.0.4",
"typescript": "^5.2.2"
"typescript": "^5.2.2",
"typia": "^5.1.6"
}
}
6 changes: 3 additions & 3 deletions web/source/settings/admin/accounts/detail.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ const { useRoute, Redirect } = require("wouter");

const query = require("../../lib/query");

const FormWithData = require("../../lib/form/form-with-data");
const FormWithData = require("../../lib/form/form-with-data").default;

const { useBaseUrl } = require("../../lib/navigation/util");
const FakeProfile = require("../../components/fake-profile");
const MutationButton = require("../../components/form/mutation-button");

const useFormSubmit = require("../../lib/form/submit");
const useFormSubmit = require("../../lib/form/submit").default;
const { useValue, useTextInput } = require("../../lib/form");
const { TextInput } = require("../../components/form/inputs");

Expand Down Expand Up @@ -77,7 +77,7 @@ function AccountDetailForm({ data: account }) {
function ModifyAccount({ account }) {
const form = {
id: useValue("id", account.id),
reason: useTextInput("text", {})
reason: useTextInput("text")
};

const [modifyAccount, result] = useFormSubmit(form, query.useActionAccountMutation());
Expand Down
254 changes: 254 additions & 0 deletions web/source/settings/admin/domain-permissions/detail.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
/*
GoToSocial
Copyright (C) GoToSocial Authors [email protected]
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 <http://www.gnu.org/licenses/>.
*/

import React from "react";

import { useMemo } from "react";
import { useLocation } from "wouter";

import { useTextInput, useBoolInput } from "../../lib/form";

import useFormSubmit from "../../lib/form/submit";

import { TextInput, Checkbox, TextArea } from "../../components/form/inputs";

import Loading from "../../components/loading";
import BackButton from "../../components/back-button";
import MutationButton from "../../components/form/mutation-button";

import { useDomainAllowsQuery, useDomainBlocksQuery } from "../../lib/query/admin/domain-permissions/get";
import { useAddDomainAllowMutation, useAddDomainBlockMutation, useRemoveDomainAllowMutation, useRemoveDomainBlockMutation } from "../../lib/query/admin/domain-permissions/update";
import { DomainPerm, PermType } from "../../lib/types/domain-permission";
import { NoArg } from "../../lib/types/query";
import { Error } from "../../components/error";

export interface DomainPermDetailProps {
baseUrl: string;
permType: PermType;
domain: string;
}

export default function DomainPermDetail({ baseUrl, permType, domain }: DomainPermDetailProps) {
const { data: domainBlocks = {}, isLoading: isLoadingDomainBlocks } = useDomainBlocksQuery(NoArg, { skip: permType !== "block" });
const { data: domainAllows = {}, isLoading: isLoadingDomainAllows } = useDomainAllowsQuery(NoArg, { skip: permType !== "allow" });

let isLoading;
switch (permType) {
case "block":
isLoading = isLoadingDomainBlocks;
break;
case "allow":
isLoading = isLoadingDomainAllows;
break;
default:
throw "perm type unknown";
}

if (domain == "view") {
// Retrieve domain from form field submission.
domain = (new URL(document.location.toString())).searchParams.get("domain")?? "unknown";
}

if (domain == "unknown") {
throw "unknown domain";
}

// Normalize / decode domain (it may be URL-encoded).
domain = decodeURIComponent(domain);

// Check if we already have a perm of the desired type for this domain.
const existingPerm: DomainPerm | undefined = useMemo(() => {
if (permType == "block") {
return domainBlocks[domain];
} else {
return domainAllows[domain];
}
}, [domainBlocks, domainAllows, domain, permType]);

let infoContent: React.JSX.Element;

if (isLoading) {
infoContent = <Loading />;
} else if (existingPerm == undefined) {
infoContent = <span>No stored {permType} yet, you can add one below:</span>;
} else {
infoContent = (
<div className="info">
<i className="fa fa-fw fa-exclamation-triangle" aria-hidden="true"></i>
<b>Editing domain permissions isn't implemented yet, <a href="https://github.com/superseriousbusiness/gotosocial/issues/1198" target="_blank" rel="noopener noreferrer">check here for progress</a></b>
</div>
);
}

return (
<div>
<h1 className="text-cutoff"><BackButton to={baseUrl} /> Domain {permType} for: <span title={domain}>{domain}</span></h1>
{infoContent}
<DomainPermForm
defaultDomain={domain}
perm={existingPerm}
permType={permType}
baseUrl={baseUrl}
/>
</div>
);
}

interface DomainPermFormProps {
defaultDomain: string;
perm?: DomainPerm;
permType: PermType;
baseUrl: string;
}

function DomainPermForm({ defaultDomain, perm, permType, baseUrl }: DomainPermFormProps) {
const isExistingPerm = perm !== undefined;
const disabledForm = isExistingPerm
? {
disabled: true,
title: "Domain permissions currently cannot be edited."
}
: {
disabled: false,
title: "",
};

const form = {
domain: useTextInput("domain", { source: perm, defaultValue: defaultDomain }),
obfuscate: useBoolInput("obfuscate", { source: perm }),
commentPrivate: useTextInput("private_comment", { source: perm }),
commentPublic: useTextInput("public_comment", { source: perm })
};

// Check which perm type we're meant to be handling
// here, and use appropriate mutations and results.
// We can't call these hooks conditionally because
// react is like "weh" (mood), but we can decide
// which ones to use conditionally.
const [ addBlock, addBlockResult ] = useAddDomainBlockMutation();
const [ removeBlock, removeBlockResult] = useRemoveDomainBlockMutation({ fixedCacheKey: perm?.id });
const [ addAllow, addAllowResult ] = useAddDomainAllowMutation();
const [ removeAllow, removeAllowResult ] = useRemoveDomainAllowMutation({ fixedCacheKey: perm?.id });

const [
addTrigger,
addResult,
removeTrigger,
removeResult,
] = useMemo(() => {
return permType == "block"
? [
addBlock,
addBlockResult,
removeBlock,
removeBlockResult,
]
: [
addAllow,
addAllowResult,
removeAllow,
removeAllowResult,
];
}, [permType,
addBlock, addBlockResult, removeBlock, removeBlockResult,
addAllow, addAllowResult, removeAllow, removeAllowResult,
]);

// Use appropriate submission params for this permType.
const [submitForm, submitFormResult] = useFormSubmit(form, [addTrigger, addResult], { changedOnly: false });

// Uppercase first letter of given permType.
const permTypeUpper = useMemo(() => {
return permType.charAt(0).toUpperCase() + permType.slice(1);
}, [permType]);

const [location, setLocation] = useLocation();

function verifyUrlThenSubmit(e) {
// Adding a new domain permissions happens on a url like
// "/settings/admin/domain-permissions/:permType/domain.com",
// but if domain input changes, that doesn't match anymore
// and causes issues later on so, before submitting the form,
// silently change url, and THEN submit.
let correctUrl = `${baseUrl}/${form.domain.value}`;
if (location != correctUrl) {
setLocation(correctUrl);
}
return submitForm(e);
}

return (
<form onSubmit={verifyUrlThenSubmit}>
<TextInput
field={form.domain}
label="Domain"
placeholder="example.com"
{...disabledForm}
/>

<Checkbox
field={form.obfuscate}
label="Obfuscate domain in public lists"
{...disabledForm}
/>

<TextArea
field={form.commentPrivate}
label="Private comment"
rows={3}
{...disabledForm}
/>

<TextArea
field={form.commentPublic}
label="Public comment"
rows={3}
{...disabledForm}
/>

<div className="action-buttons row">
<MutationButton
label={permTypeUpper}
result={submitFormResult}
showError={false}
{...disabledForm}
/>

{
isExistingPerm &&
<MutationButton
type="button"
onClick={() => removeTrigger(perm.id?? "")}
label="Remove"
result={removeResult}
className="button danger"
showError={false}
disabled={!isExistingPerm}
/>
}
</div>

<>
{addResult.error && <Error error={addResult.error} />}
{removeResult.error && <Error error={removeResult.error} />}
</>

</form>
);
}
Loading

0 comments on commit 637f188

Please sign in to comment.