Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions backend/src/router/thread.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ func updateThread(w http.ResponseWriter, r *http.Request) {
return
}

if credentials.Role.AccessLevel() != 0 && p.Member != credentials.ID {
if credentials.Role.AccessLevel() > 1 && p.Member != credentials.ID {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this change necessary?

@LeoPlix LeoPlix Apr 9, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, from what I gathered admin has access level 0 and coordinators have access level 1, so it was the only way to garantee access to edit to both roles

http.Error(w, "Unauthorized", http.StatusForbidden)
return
}
Expand Down Expand Up @@ -245,7 +245,7 @@ func deleteThread(w http.ResponseWriter, r *http.Request) {
return
}

if credentials.Role.AccessLevel() != 0 && p.Member != credentials.ID {
if credentials.Role.AccessLevel() > 1 && p.Member != credentials.ID {
http.Error(w, "Unauthorized", http.StatusForbidden)
return
}
Expand Down
2 changes: 1 addition & 1 deletion frontend/.env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
VITE_GOOGLE_CLIENT_ID=balls.apps.googleusercontent.com
VITE_GOOGLE_SCOPE="email profile openid https://www.googleapis.com/auth/gmail.compose"
VITE_API_URL=http://localhost:8080
VITE_API_URL=http://localhost:8080
37 changes: 33 additions & 4 deletions frontend/src/components/Communications.vue
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,10 @@
</div>

<!-- Actions -->
<div :class="['mt-1 transition-opacity opacity-100']">
<div
v-if="canManageThread(thread)"
:class="['mt-1 transition-opacity opacity-100']"
>
<button
class="p-1 rounded focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-black/10 text-muted-foreground hover:opacity-80"
title="Edit"
Expand Down Expand Up @@ -459,6 +462,7 @@ import Textarea from "./ui/textarea/Textarea.vue";
import { useUpdatePostMutation } from "@/mutations/posts.ts";
import { Pencil, Trash2, Mail, RefreshCw } from "lucide-vue-next";
import { useDeleteThreadMutation } from "@/mutations/threads.ts";
import { usePermissions } from "@/composables/usePermissions";
import GmailThreadPicker from "./GmailThreadPicker.vue";
import {
Tooltip,
Expand Down Expand Up @@ -503,6 +507,8 @@ const selectedEventId = ref<number | null>(
eventStore.selectedEvent?.id ?? null,
);
const selectedTemplate = ref<TemplateWithVariables>();
const authStore = useAuthStore();
const { isAdmin, isCoordinator } = usePermissions();

const editingThreadId = ref<string | null>(null);
const editingPostId = ref<string | null>(null);
Expand Down Expand Up @@ -595,8 +601,23 @@ const onMessageInput = () => {
props.postThreadMutation?.reset?.();
};

const currentMemberId = computed(() => {
return authStore.member?.id ?? authStore.decoded?.id ?? null;
});

const canManageThread = (thread: ThreadWithEntry): boolean => {
if (isCoordinator.value) return true;
if (isAdmin.value) return true;

return Boolean(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why use Boolean(...)?

thread.entry?.member &&
currentMemberId.value &&
thread.entry.member === currentMemberId.value,
);
};

const startEdit = (thread: ThreadWithEntry) => {
if (!thread.entry) return;
if (!thread.entry || !canManageThread(thread)) return;

editingThreadId.value = thread.id;
editingPostId.value = thread.entry.id;
Expand Down Expand Up @@ -647,7 +668,6 @@ const handleGmailThreadsSave = async (threadIds: string[]) => {
};

// Gmail sync functionality
const authStore = useAuthStore();
const gmailComposable = useGmailMessages();
const { requestGoogleToken, error: googleAuthError } = useGoogleAuth();
const isSyncing = ref(false);
Expand Down Expand Up @@ -863,6 +883,15 @@ const saveEdit = async () => {
const text = editText.value.trim();
if (!id || !text) return;

const thread = sortedCommunications.value.find(
(item) => item.id === editingThreadId.value,
);

if (!thread || !canManageThread(thread)) {
cancelEdit();
return;
}

updatePostMutation.postId.value = id;
updatePostMutation.text.value = text;

Expand All @@ -880,7 +909,7 @@ const saveEdit = async () => {
};

const requestDelete = async (thread: ThreadWithEntry) => {
if (!thread.entry) return;
if (!thread.entry || !canManageThread(thread)) return;
const ok = window.confirm(
"Delete this message? This action cannot be undone.",
);
Expand Down
170 changes: 170 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove this file

# yarn lockfile v1


ansi-regex@^5.0.1:
version "5.0.1"
resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz"
integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==

ansi-styles@^4.0.0, ansi-styles@^4.1.0:
version "4.3.0"
resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz"
integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
dependencies:
color-convert "^2.0.1"

chalk@4.1.2:
version "4.1.2"
resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz"
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
dependencies:
ansi-styles "^4.1.0"
supports-color "^7.1.0"

cliui@^8.0.1:
version "8.0.1"
resolved "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz"
integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==
dependencies:
string-width "^4.2.0"
strip-ansi "^6.0.1"
wrap-ansi "^7.0.0"

color-convert@^2.0.1:
version "2.0.1"
resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz"
integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
dependencies:
color-name "~1.1.4"

color-name@~1.1.4:
version "1.1.4"
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==

concurrently@^9.0.1:
version "9.2.1"
resolved "https://registry.npmjs.org/concurrently/-/concurrently-9.2.1.tgz"
integrity sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==
dependencies:
chalk "4.1.2"
rxjs "7.8.2"
shell-quote "1.8.3"
supports-color "8.1.1"
tree-kill "1.2.2"
yargs "17.7.2"

emoji-regex@^8.0.0:
version "8.0.0"
resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz"
integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==

escalade@^3.1.1:
version "3.2.0"
resolved "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz"
integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==

get-caller-file@^2.0.5:
version "2.0.5"
resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz"
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==

has-flag@^4.0.0:
version "4.0.0"
resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz"
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==

is-fullwidth-code-point@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz"
integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==

require-directory@^2.1.1:
version "2.1.1"
resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz"
integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==

rxjs@7.8.2:
version "7.8.2"
resolved "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz"
integrity sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==
dependencies:
tslib "^2.1.0"

shell-quote@1.8.3:
version "1.8.3"
resolved "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz"
integrity sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==

string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"

strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
dependencies:
ansi-regex "^5.0.1"

supports-color@^7.1.0:
version "7.2.0"
resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz"
integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
dependencies:
has-flag "^4.0.0"

supports-color@8.1.1:
version "8.1.1"
resolved "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz"
integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==
dependencies:
has-flag "^4.0.0"

tree-kill@1.2.2:
version "1.2.2"
resolved "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz"
integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==

tslib@^2.1.0:
version "2.8.1"
resolved "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz"
integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==

wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
dependencies:
ansi-styles "^4.0.0"
string-width "^4.1.0"
strip-ansi "^6.0.0"

y18n@^5.0.5:
version "5.0.8"
resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz"
integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==

yargs-parser@^21.1.1:
version "21.1.1"
resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz"
integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==

yargs@17.7.2:
version "17.7.2"
resolved "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz"
integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==
dependencies:
cliui "^8.0.1"
escalade "^3.1.1"
get-caller-file "^2.0.5"
require-directory "^2.1.1"
string-width "^4.2.3"
y18n "^5.0.5"
yargs-parser "^21.1.1"
Loading