Add public map with JWT admin login and protect admin APIs.

Keep map reads open; gate admin UI/nav and object mutations behind env-based admin credentials, and default parser batch limit to 10.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-16 23:04:21 +03:00
co-authored by Cursor
parent 52185aeaa8
commit 8ab606747f
23 changed files with 564 additions and 41 deletions
@@ -4,11 +4,15 @@ import { onMounted, onUnmounted, watch } from "vue";
import { useLeafletMap } from "../composables/useLeafletMap";
import type { MapObjectWithEvent } from "../types/map";
const props = defineProps<{
objects: MapObjectWithEvent[];
selectedId: number | null;
openPopupId: number | null;
}>();
const props = withDefaults(
defineProps<{
objects: MapObjectWithEvent[];
selectedId: number | null;
openPopupId: number | null;
editable?: boolean;
}>(),
{ editable: false },
);
const emit = defineEmits<{
select: [object: MapObjectWithEvent];
@@ -53,7 +57,7 @@ function buildPopupHtml(obj: MapObjectWithEvent): string {
}
function isDraggable(obj: MapObjectWithEvent): boolean {
return obj.id === props.selectedId && !obj.event_id;
return props.editable && obj.id === props.selectedId && !obj.event_id;
}
function syncMarkers() {
@@ -170,7 +174,7 @@ onUnmounted(() => {
});
watch(
() => [props.objects, props.selectedId] as const,
() => [props.objects, props.selectedId, props.editable] as const,
() => {
syncMarkers();
panToSelected(false);
@@ -9,9 +9,13 @@ import {
import { MEDIA_ACCEPT, OBJECT_TYPE_LABELS } from "../types/object";
import type { MapObjectWithEvent } from "../types/map";
const props = defineProps<{
object: MapObjectWithEvent | null;
}>();
const props = withDefaults(
defineProps<{
object: MapObjectWithEvent | null;
canEdit?: boolean;
}>(),
{ canEdit: false },
);
const emit = defineEmits<{
openEvents: [];
@@ -151,7 +155,7 @@ watch(() => props.object?.id, () => loadMedia(), { immediate: true });
<section v-if="!isIngested" class="media-section">
<div class="media-header">
<h4>Медиа</h4>
<label class="upload-btn">
<label v-if="canEdit" class="upload-btn">
{{ uploading ? "Загрузка..." : "Добавить" }}
<input
type="file"
@@ -185,7 +189,14 @@ watch(() => props.object?.id, () => loadMedia(), { immediate: true });
<span class="name" :title="item.original_name">{{ item.original_name }}</span>
<span class="size">{{ formatFileSize(item.size) }}</span>
</div>
<button type="button" class="delete-btn" @click="handleDelete(item.id)">Удалить</button>
<button
v-if="canEdit"
type="button"
class="delete-btn"
@click="handleDelete(item.id)"
>
Удалить
</button>
</li>
</ul>
</section>