Skip to content
Open
Show file tree
Hide file tree
Changes from all 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?

Copy link
Copy Markdown
Author

@LeoPlix LeoPlix Apr 9, 2026

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 (
thread.entry?.member != null &&
currentMemberId.value != null &&
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
Loading