Add plugin platform with modular backend and frontend registry.

Split graph/import/meta into Django apps, add API v1 with OpenAPI and pytest, and introduce plugin registry with the tags reference plugin on both FE and BE.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-25 09:35:51 +03:00
co-authored by Cursor
parent b694a596b8
commit cafc7335ad
77 changed files with 1801 additions and 490 deletions
+6 -1
View File
@@ -13,6 +13,10 @@ vi.mock('../application/usecases/networkMaps', () => ({
listMembershipsByContact: vi.fn(async () => []),
}))
vi.mock('../core/pluginRegistry', () => ({
getContactFormExtensions: () => [],
}))
describe('ContactForm', () => {
beforeEach(() => {
vi.clearAllMocks()
@@ -21,10 +25,11 @@ describe('ContactForm', () => {
it('emits submit with contact data and map ids', async () => {
const wrapper = mount(ContactForm, { props: { initial: { name: 'Иван' } } })
await wrapper.get('form').trigger('submit.prevent')
const [contactData, mapIds] = wrapper.emitted('submit')[0]
const [contactData, mapIds, pluginPayload] = wrapper.emitted('submit')[0]
expect(contactData.name).toBe('Иван')
expect(contactData.include_on_network_map).toBeUndefined()
expect(mapIds).toEqual([])
expect(pluginPayload).toEqual({ tags: [] })
})
it('shows delete button when editing existing contact', async () => {
+12 -2
View File
@@ -45,6 +45,13 @@
<label>Заметки</label>
<textarea v-model="form.notes" class="form-control" placeholder="Дополнительная информация..." rows="3"></textarea>
</div>
<component
:is="Ext"
v-for="(Ext, index) in contactFormExtensions"
:key="index"
:contact-id="initial?.id"
v-model="pluginTags"
/>
<div class="contact-form-footer">
<button
v-if="showDelete"
@@ -63,9 +70,10 @@
</template>
<script setup>
import { reactive, watch, computed, onMounted } from 'vue'
import { reactive, ref, watch, computed, onMounted } from 'vue'
import { useNetworkMapsStore } from '../stores/networkMaps'
import { listMembershipsByContact } from '../application/usecases/networkMaps'
import { getContactFormExtensions } from '../core/pluginRegistry'
const props = defineProps({
initial: { type: Object, default: () => ({}) },
@@ -75,6 +83,8 @@ const props = defineProps({
const emit = defineEmits(['submit', 'cancel', 'delete'])
const mapsStore = useNetworkMapsStore()
const contactFormExtensions = getContactFormExtensions()
const pluginTags = ref([])
const showDelete = computed(() => {
if (props.deletable) return true
@@ -140,7 +150,7 @@ onMounted(async () => {
function onSubmit() {
const { mapIds, ...contactData } = form
emit('submit', contactData, mapIds)
emit('submit', contactData, mapIds, { tags: [...pluginTags.value] })
}
</script>