diff --git a/bats_ai/core/views/nabat/nabat_recording.py b/bats_ai/core/views/nabat/nabat_recording.py
index dd6d2353..c25a11d6 100644
--- a/bats_ai/core/views/nabat/nabat_recording.py
+++ b/bats_ai/core/views/nabat/nabat_recording.py
@@ -231,8 +231,6 @@ def generate_nabat_recording(
@router.get('/{id}/spectrogram', auth=admin_auth)
def get_spectrogram(request: HttpRequest, id: int):
- if not request.user.is_authenticated and not request.user.is_superuser:
- return JsonResponse({'error': 'Permission denied'}, status=403)
try:
nabat_recording = NABatRecording.objects.get(pk=id)
except NABatRecording.DoesNotExist:
@@ -432,7 +430,9 @@ def get_recording_annotation_details(request: HttpRequest, id: int, apiToken: st
@router.put('recording-annotation', auth=None, response={200: str})
def create_recording_annotation(request: HttpRequest, data: NABatCreateRecordingAnnotationSchema):
- email_or_response = get_email_if_authorized(request, data.apiToken, recording_pk=id)
+ email_or_response = get_email_if_authorized(
+ request, data.apiToken, recording_pk=data.recordingId
+ )
if isinstance(email_or_response, JsonResponse):
return email_or_response
user_email = email_or_response # safe to use
@@ -487,11 +487,12 @@ def create_recording_annotation(request: HttpRequest, data: NABatCreateRecording
def update_recording_annotation(
request: HttpRequest, id: int, data: NABatCreateRecordingAnnotationSchema
):
- email_or_response = get_email_if_authorized(request, data.apiToken, recording_pk=id)
+ email_or_response = get_email_if_authorized(
+ request, data.apiToken, recording_pk=data.recordingId
+ )
if isinstance(email_or_response, JsonResponse):
return email_or_response
user_email = email_or_response # safe to use
-
try:
annotation = NABatRecordingAnnotation.objects.get(pk=id, user_email=user_email)
# Check permission
@@ -539,9 +540,10 @@ def update_recording_annotation(
return JsonResponse({'error': 'One or more species IDs not found.'}, 404)
+# TODO: Determine if this will be implemented for NABat
@router.delete('recording-annotation/{id}', auth=None, response={200: str})
-def delete_recording_annotation(request: HttpRequest, id: int, apiToken: str):
- email_or_response = get_email_if_authorized(request, apiToken, recording_pk=id)
+def delete_recording_annotation(request: HttpRequest, id: int, apiToken: str, recordingId: str):
+ email_or_response = get_email_if_authorized(request, apiToken, recording_pk=recordingId)
if isinstance(email_or_response, JsonResponse):
return email_or_response
user_email = email_or_response # safe to use
diff --git a/client/src/api/NABatApi.ts b/client/src/api/NABatApi.ts
index 0d82beaa..7359a525 100644
--- a/client/src/api/NABatApi.ts
+++ b/client/src/api/NABatApi.ts
@@ -68,8 +68,8 @@ async function patchNABatFileAnnotation(fileAnnotationId: number, fileAnnotation
return axiosInstance.patch<{ message: string, id: number }>(`nabat/recording/recording-annotation/${fileAnnotationId}`, { ...fileAnnotation });
}
-async function deleteNABatFileAnnotation(fileAnnotationId: number, apiToken?: string) {
- return axiosInstance.delete<{ message: string, id: number }>(`nabat/recording/recording-annotation/${fileAnnotationId}`, { params: { apiToken } });
+async function deleteNABatFileAnnotation(fileAnnotationId: number, apiToken?: string, recordingId?: number) {
+ return axiosInstance.delete<{ message: string, id: number }>(`nabat/recording/recording-annotation/${fileAnnotationId}`, { params: { apiToken, recordingId } });
}
export interface RecordingListItem {
diff --git a/client/src/components/ColorSchemeSelect.vue b/client/src/components/ColorSchemeSelect.vue
index 67f501a8..9f0c54f8 100644
--- a/client/src/components/ColorSchemeSelect.vue
+++ b/client/src/components/ColorSchemeSelect.vue
@@ -14,6 +14,10 @@ defineProps({
type: Number,
default: 150,
},
+ maxWidth: {
+ type: Number,
+ default: 150,
+ },
returnObject: {
type: Boolean,
default: true,
diff --git a/client/src/components/RecordingAnnotationEditor.vue b/client/src/components/RecordingAnnotationEditor.vue
index dfc8f999..df86eb2d 100644
--- a/client/src/components/RecordingAnnotationEditor.vue
+++ b/client/src/components/RecordingAnnotationEditor.vue
@@ -95,7 +95,7 @@ export default defineComponent({
const deleteAnnotation = async () => {
if (props.annotation && props.recordingId) {
- props.type === 'nabat' ? await deleteNABatFileAnnotation(props.annotation.id, props.apiToken) : await deleteFileAnnotation(props.annotation.id,);
+ props.type === 'nabat' ? await deleteNABatFileAnnotation(props.annotation.id, props.apiToken, props.recordingId) : await deleteFileAnnotation(props.annotation.id,);
emit('delete:annotation');
}
};
@@ -119,7 +119,7 @@ export default defineComponent({
Edit Annotations
= ref(null);
+
+
+
const loadFileAnnotations = async () => {
if (props.type === 'nabat') {
annotations.value = (await getNABatRecordingFileAnnotations(props.recordingId, props.apiToken)).data;
@@ -51,7 +56,20 @@ export default defineComponent({
}
};
- onMounted(() => loadFileAnnotations());
+ onMounted(async () => {
+ await loadFileAnnotations();
+ if (props.type === 'nabat') {
+ const decoded = decodeJWT(props.apiToken);
+ if (decoded['email']) {
+ currentNaBatUser.value = decoded['email'];
+ const foundItem = annotations.value.find((item) => item.owner === currentNaBatUser.value);
+ if (foundItem) {
+ setSelectedId(foundItem);
+ }
+ }
+ }
+
+ });
const addAnnotation = async () => {
const newAnnotation: UpdateFileAnnotation & { apiToken?: string } = {
@@ -91,11 +109,11 @@ export default defineComponent({
const isAdmin = computed(() => configuration.value.is_admin);
const disableNaBatAnnotations = computed(() => {
- const nonAIAnnotations = annotations.value.filter((item) => item.owner);
+ const currentUserAnnotations = annotations.value.filter((item) => item.owner === currentNaBatUser.value);
if (isAdmin.value && props.type === 'nabat' && !props.apiToken) {
return true;
}
- return (nonAIAnnotations.length > 0 && props.type === 'nabat');
+ return ( currentUserAnnotations.length > 0 && props.type === 'nabat');
});
return {
@@ -109,6 +127,7 @@ export default defineComponent({
detailsDialog,
detailRecordingId,
disableNaBatAnnotations,
+ currentNaBatUser,
};
},
});
@@ -137,6 +156,7 @@ export default defineComponent({
:id="`annotation-${annotation.id}`"
:key="annotation.id"
:class="{ selected: annotation.id === selectedAnnotation?.id }"
+ :disabled="type === 'nabat' && disableNaBatAnnotations && annotation.owner !== currentNaBatUser"
class="annotation-item"
@click="setSelectedId(annotation)"
>
diff --git a/client/src/use/useJWTToken.ts b/client/src/use/useJWTToken.ts
index 7a854af8..cd31c677 100644
--- a/client/src/use/useJWTToken.ts
+++ b/client/src/use/useJWTToken.ts
@@ -1,29 +1,29 @@
-import { ref, watch } from 'vue';
+import { ref, watch } from "vue";
interface UseJWTTokenOptions {
token: string;
warningSeconds: number;
}
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+export function decodeJWT(token: string): any | null {
+ try {
+ const payload = token.split(".")[1];
+ const decoded = atob(payload.replace(/-/g, "+").replace(/_/g, "/"));
+ return JSON.parse(decoded);
+ } catch (error) {
+ console.error("Failed to decode JWT:", error);
+ return null;
+ }
+}
+
export function useJWTToken(options: UseJWTTokenOptions) {
const { token, warningSeconds } = options;
- const storageKey = 'jwt-expiration';
+ const storageKey = "jwt-expiration";
const exp = ref(null);
const shouldWarn = ref(false);
let warningTimeout: ReturnType | null = null;
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- function decodeJWT(token: string): any | null {
- try {
- const payload = token.split('.')[1];
- const decoded = atob(payload.replace(/-/g, '+').replace(/_/g, '/'));
- return JSON.parse(decoded);
- } catch (error) {
- console.error('Failed to decode JWT:', error);
- return null;
- }
- }
-
function setupWarning(expiration: number) {
const now = Math.floor(Date.now() / 1000);
const secondsUntilWarning = expiration - now - warningSeconds;
@@ -51,11 +51,11 @@ export function useJWTToken(options: UseJWTTokenOptions) {
if (stored) {
try {
const data = JSON.parse(stored);
- if (typeof data.expiration === 'number') {
+ if (typeof data.expiration === "number") {
return data.expiration;
}
} catch (error) {
- console.error('Failed to parse stored expiration:', error);
+ console.error("Failed to parse stored expiration:", error);
}
}
return null;
@@ -66,12 +66,12 @@ export function useJWTToken(options: UseJWTTokenOptions) {
return;
}
const decoded = decodeJWT(token);
- if (decoded && typeof decoded.exp === 'number') {
+ if (decoded && typeof decoded.exp === "number") {
exp.value = decoded.exp;
persistExpiration(decoded.exp);
setupWarning(decoded.exp);
} else {
- console.warn('Token does not have a valid exp field.');
+ console.warn("Token does not have a valid exp field.");
const persisted = loadPersistedExpiration();
if (persisted) {
exp.value = persisted;
@@ -101,4 +101,4 @@ export function useJWTToken(options: UseJWTTokenOptions) {
shouldWarn,
clear,
};
-}
\ No newline at end of file
+}