Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add ability to delete groups with no markets (#2521)
* Add ability to delete groups * Instruct user to untag markets first * Check permissions * replace instead of push * log the deleted group
- Loading branch information
Showing
5 changed files
with
181 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import { isModId } from 'common/envs/constants' | ||
import { run } from 'common/supabase/utils' | ||
import { log } from 'shared/log' | ||
import { | ||
createSupabaseClient, | ||
createSupabaseDirectClient, | ||
} from 'shared/supabase/init' | ||
import { APIError, type AuthedUser } from './helpers/endpoint' | ||
|
||
export const deleteGroup = async ( | ||
props: { id: string } | { slug: string }, | ||
auth: AuthedUser | ||
) => { | ||
const db = createSupabaseClient() | ||
const pg = createSupabaseDirectClient() | ||
|
||
const q = db.from('groups').select() | ||
if ('id' in props) { | ||
q.eq('id', props.id) | ||
} else { | ||
q.eq('slug', props.slug) | ||
} | ||
|
||
const { data: groups } = await run(q) | ||
|
||
if (groups.length == 0) { | ||
throw new APIError(404, 'Group not found') | ||
} | ||
|
||
const group = groups[0] | ||
|
||
log( | ||
`delete group ${group.name} ${group.slug} initiated by ${auth.uid}`, | ||
group | ||
) | ||
|
||
const id = group.id | ||
|
||
if (!isModId(auth.uid)) { | ||
const requester = await pg.oneOrNone( | ||
'select role from group_members where group_id = $1 and member_id = $2', | ||
[id, auth.uid] | ||
) | ||
|
||
if (requester?.role !== 'admin') { | ||
throw new APIError(403, 'You do not have permission to delete this group') | ||
} | ||
} | ||
|
||
// fail if there are contracts tagged with this group | ||
// we could just untag contracts like in scripts/deleteGroup.ts | ||
// but I don't trust the mods. I'm forcing them to manually untag or retag contracts to make them reckon with the responsibility of what deleting a group means. | ||
const { count: contractCount } = await pg.one( | ||
`select count(*) from group_contracts where group_id = $1`, | ||
[id] | ||
) | ||
|
||
if (contractCount > 0) { | ||
throw new APIError( | ||
400, | ||
`Only topics with no questions can be deleted. There are still ${contractCount} questions tagged with this topic.` | ||
) | ||
} | ||
|
||
await pg.tx(async (tx) => { | ||
log('removing group members') | ||
await tx.none('delete from group_members where group_id = $1', [id]) | ||
log('deleting group ', id) | ||
await tx.none('delete from groups where id = $1', [id]) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
import { type PrivacyStatusType } from 'common/group' | ||
import { useRouter } from 'next/router' | ||
import { useState } from 'react' | ||
import toast from 'react-hot-toast' | ||
import { api } from 'web/lib/firebase/api' | ||
import { Button } from '../buttons/button' | ||
import { Modal } from '../layout/modal' | ||
import { Input } from '../widgets/input' | ||
import { Title } from '../widgets/title' | ||
|
||
export function DeleteTopicModal(props: { | ||
group: { id: string; name: string; privacyStatus: PrivacyStatusType } | ||
open: boolean | ||
setOpen: (open: boolean) => void | ||
}) { | ||
const { open, setOpen } = props | ||
const { name, id, privacyStatus } = props.group | ||
|
||
const [loading, setLoading] = useState(false) | ||
const [confirm, setConfirm] = useState('') | ||
const [error, setError] = useState('') | ||
|
||
const router = useRouter() | ||
|
||
return ( | ||
<Modal | ||
open={open} | ||
setOpen={setOpen} | ||
className="bg-canvas-50 rounded-xl p-4 sm:p-6" | ||
size="md" | ||
> | ||
<Title>Delete {name}?</Title> | ||
<p className="mb-2"> | ||
Deleting a topic is permanent. All admins and followers will be removed | ||
and no one will be able to find this topic anywhere. | ||
</p> | ||
{privacyStatus === 'public' && ( | ||
<p className="mb-2"> | ||
Topics should only be deleted if they are low quality or duplicate. | ||
Ask @moderators on discord if you aren't sure. | ||
</p> | ||
)} | ||
<p className="mb-2"> | ||
To delete, first untag all questions tagged with this topic, then type " | ||
{name}" below to confirm. | ||
</p> | ||
|
||
<Input | ||
placeholder="The name of this group" | ||
className="mb-2 mt-2 w-full" | ||
value={confirm} | ||
onChange={(e) => setConfirm(e.target.value)} | ||
/> | ||
|
||
<Button | ||
onClick={() => { | ||
setLoading(true) | ||
api('group/by-id/:id/delete', { id }) | ||
.then(() => { | ||
setLoading(false) | ||
toast.success('Topic deleted') | ||
router.replace('/browse') | ||
}) | ||
.catch((e) => { | ||
setLoading(false) | ||
console.error(e) | ||
setError(e.message || 'Failed to delete topic') | ||
}) | ||
}} | ||
color="red" | ||
disabled={loading || confirm != name} | ||
size="xl" | ||
className="w-full" | ||
> | ||
{loading ? 'Deleting...' : 'Delete Topic'} | ||
</Button> | ||
|
||
{error && <p className="mt-2 text-red-500">{error}</p>} | ||
</Modal> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters