Skip to content

Commit 8f77fbf

Browse files
committed
feat: make join requests disableable
1 parent 5850642 commit 8f77fbf

File tree

9 files changed

+151
-14
lines changed

9 files changed

+151
-14
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { trpc } from 'src/trpc/server';
22

33
import { makePublicProcedure } from './make-public';
4+
import { setJoinRequestsAllowedProcedure } from './set-join-requests-allowed';
45

56
export const privacyRouter = trpc.router({
67
makePublic: makePublicProcedure(),
8+
setJoinRequestsAllowed: setJoinRequestsAllowedProcedure(),
79
});
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { isNanoID } from '@stdlib/misc';
2+
import { checkRedlockSignalAborted } from '@stdlib/redlock';
3+
import { once } from 'lodash';
4+
import type { InferProcedureOpts } from 'src/trpc/helpers';
5+
import { authProcedure } from 'src/trpc/helpers';
6+
import { z } from 'zod';
7+
8+
const baseProcedure = authProcedure.input(
9+
z.object({
10+
groupId: z.string().refine(isNanoID),
11+
12+
areJoinRequestsAllowed: z.boolean(),
13+
}),
14+
);
15+
16+
export const setJoinRequestsAllowedProcedure = once(() =>
17+
baseProcedure.mutation(setJoinRequestsAllowed),
18+
);
19+
20+
export async function setJoinRequestsAllowed({
21+
ctx,
22+
input,
23+
}: InferProcedureOpts<typeof baseProcedure>) {
24+
return await ctx.usingLocks(
25+
[[`user-lock:${ctx.userId}`], [`group-lock:${input.groupId}`]],
26+
async (signals) => {
27+
return await ctx.dataAbstraction.transaction(async (dtrx) => {
28+
// Assert agent is subscribed
29+
30+
await ctx.assertUserSubscribed({ userId: ctx.userId });
31+
32+
// Check if user has sufficient permissions
33+
34+
await ctx.assertSufficientGroupPermissions({
35+
userId: ctx.userId,
36+
groupId: input.groupId,
37+
permission: 'editGroupSettings',
38+
});
39+
40+
await ctx.dataAbstraction.hmset(
41+
'group',
42+
input.groupId,
43+
{ 'are-join-requests-allowed': input.areJoinRequestsAllowed },
44+
{ dtrx },
45+
);
46+
47+
checkRedlockSignalAborted(signals);
48+
});
49+
},
50+
);
51+
}

apps/app-server/src/websocket/groups/join-requests/send.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,17 @@ export async function sendStep1({
5555

5656
await ctx.assertUserSubscribed({ userId: ctx.userId });
5757

58-
const [groupJoinRequestRejected, groupMemberRole] = await Promise.all([
58+
const [
59+
groupAreJoinRequestsAllowed,
60+
groupJoinRequestRejected,
61+
groupMemberRole,
62+
] = await Promise.all([
63+
ctx.dataAbstraction.hget(
64+
'group',
65+
input.groupId,
66+
'are-join-requests-allowed',
67+
),
68+
5969
ctx.dataAbstraction.hget(
6070
'group-join-request',
6171
`${input.groupId}:${ctx.userId}`,
@@ -69,6 +79,15 @@ export async function sendStep1({
6979
),
7080
]);
7181

82+
// Check if group allows join requests
83+
84+
if (!groupAreJoinRequestsAllowed) {
85+
throw new TRPCError({
86+
code: 'FORBIDDEN',
87+
message: 'This group does not allow join requests.',
88+
});
89+
}
90+
7291
// Check if user has been rejected from the group
7392

7493
if (groupJoinRequestRejected) {

apps/client/src/layouts/PagesLayout/MainContent/DisplayPage/DisplayScreens/DisplayUnauthorizedScreen.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
v-if="
66
uiStore().loggedIn &&
77
!realtimeCtx.loading &&
8-
!realtimeCtx.hget('group', page.react.groupId, 'is-personal')
8+
!realtimeCtx.hget('group', page.react.groupId, 'is-personal') &&
9+
realtimeCtx.hget('group', page.react.groupId, 'are-join-requests-allowed')
910
"
1011
>
1112
<Gap style="height: 12px" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<template>
2+
<Checkbox
3+
@update:model-value="(value) => setJoinRequestsAllowed(value)"
4+
:model-value="
5+
internals.realtime.globalCtx.hget(
6+
'group',
7+
groupId,
8+
'are-join-requests-allowed',
9+
)
10+
"
11+
label="Allow join requests"
12+
/>
13+
</template>
14+
15+
<script setup lang="ts">
16+
import { handleError } from 'src/code/utils/misc';
17+
18+
const groupId = inject<string>('groupId')!;
19+
20+
async function setJoinRequestsAllowed(value: boolean) {
21+
try {
22+
await trpcClient.groups.privacy.setJoinRequestsAllowed.mutate({
23+
groupId,
24+
25+
areJoinRequestsAllowed: value,
26+
});
27+
28+
if (value) {
29+
$quasar().notify({
30+
message: 'Join requests are now enabled in this group.',
31+
type: 'positive',
32+
});
33+
} else {
34+
$quasar().notify({
35+
message: 'Join requests are now disabled in this group.',
36+
type: 'positive',
37+
});
38+
}
39+
} catch (error) {
40+
handleError(error);
41+
}
42+
}
43+
</script>

apps/client/src/layouts/PagesLayout/RightSidebar/PageProperties/GroupSettingsDialog/GeneralTab/GeneralTab.vue

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -97,16 +97,16 @@
9797
/>
9898
</template>
9999

100-
<Gap style="height: 24px" />
100+
<Gap style="height: 28px" />
101101

102102
<q-separator />
103103

104104
<Gap style="height: 20px" />
105105
</template>
106106

107-
<div>Group infos</div>
107+
<b>Group infos</b>
108108

109-
<Gap style="height: 8px" />
109+
<Gap style="height: 20px" />
110110

111111
<TextField
112112
label="Group ID"
@@ -166,15 +166,15 @@
166166
]?.permissions.editGroupSettings
167167
"
168168
>
169-
<Gap style="height: 24px" />
169+
<Gap style="height: 28px" />
170170

171171
<q-separator />
172172

173173
<Gap style="height: 20px" />
174174

175-
<div>Group security</div>
175+
<b>Group security</b>
176176

177-
<Gap style="height: 10px" />
177+
<Gap style="height: 20px" />
178178

179179
<div style="max-width: 300px; display: flex; flex-direction: column">
180180
<template v-if="groupId !== internals.personalGroupId">
@@ -217,17 +217,21 @@
217217
]?.permissions.editGroupSettings
218218
"
219219
>
220-
<Gap style="height: 24px" />
220+
<Gap style="height: 28px" />
221221

222222
<q-separator />
223223

224224
<Gap style="height: 20px" />
225225

226-
<div>Group privacy</div>
226+
<b>Group privacy</b>
227227

228-
<Gap style="height: 10px" />
228+
<Gap style="height: 20px" />
229229

230230
<div style="max-width: 300px; display: flex; flex-direction: column">
231+
<AllowJoinRequestsCheckbox />
232+
233+
<Gap style="height: 16px" />
234+
231235
<MakePrivateBtn
232236
v-if="
233237
internals.realtime.globalCtx.hget('group', groupId, 'is-public')
@@ -249,15 +253,15 @@
249253
]?.permissions.editGroupSettings
250254
"
251255
>
252-
<Gap style="height: 24px" />
256+
<Gap style="height: 28px" />
253257

254258
<q-separator />
255259

256260
<Gap style="height: 20px" />
257261

258-
<div>Danger zone</div>
262+
<b>Danger zone</b>
259263

260-
<Gap style="height: 10px" />
264+
<Gap style="height: 20px" />
261265

262266
<div style="max-width: 300px; display: flex; flex-direction: column">
263267
<DeleteGroupBtn />
@@ -276,6 +280,7 @@ import type { RealtimeContext } from 'src/code/realtime/context';
276280
import { isCtrlDown } from 'src/code/utils/misc';
277281
import type { Ref } from 'vue';
278282
283+
import AllowJoinRequestsCheckbox from './AllowJoinRequestsCheckbox.vue';
279284
import DeleteGroupBtn from './DeleteGroupBtn.vue';
280285
import MakePrivateBtn from './Privacy/MakePrivateBtn.vue';
281286
import MakePublicBtn from './Privacy/MakePublicBtn.vue';
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import type { GroupModel } from '@deeplib/db';
2+
import type { DataField } from '@stdlib/data';
3+
4+
export const areJoinRequestsAllowed: DataField<GroupModel> = {
5+
notifyUpdates: true,
6+
7+
userGettable: () => true,
8+
9+
columns: ['are_join_requests_allowed'],
10+
11+
get: ({ model }) => model?.are_join_requests_allowed === true,
12+
};

packages/@deeplib/data/src/data-hashes/group/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { validateDataHash } from '@stdlib/data/src/universal';
22
import { once } from 'lodash';
33

44
import { accessKeyring } from './access-keyring';
5+
import { areJoinRequestsAllowed } from './are-join-requests-allowed';
56
import { encryptedContentKeyring } from './encrypted-content-keyring';
67
import { encryptedName } from './encrypted-name';
78
import { encryptedPrivateKeyring } from './encrypted-private-keyring';
@@ -38,5 +39,6 @@ export const group = validateDataHash({
3839
'permanent-deletion-date': permanentDeletionDate,
3940
'public-keyring': publicKeyring,
4041
'user-id': userId,
42+
'are-join-requests-allowed': areJoinRequestsAllowed,
4143
},
4244
});

packages/@deeplib/db/src/models/group.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,6 @@ export class GroupModel extends Model {
2020
encrypted_private_keyring!: Uint8Array;
2121

2222
permanent_deletion_date!: Date | null;
23+
24+
are_join_requests_allowed!: boolean;
2325
}

0 commit comments

Comments
 (0)