feat: import
Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* Contact Import Store
|
||||
*/
|
||||
|
||||
import { computed, ref } from 'vue'
|
||||
import { defineStore } from 'pinia'
|
||||
import { entityService } from '../services/entityService'
|
||||
import type { CollectionIdentifier } from '../types/common'
|
||||
import type { EntityImportResponse } from '../types/entity'
|
||||
import type {
|
||||
ImportCounters,
|
||||
ImportFileAdd,
|
||||
ImportFileEntry,
|
||||
ImportFileOptions,
|
||||
ImportSession,
|
||||
ImportSessionStage
|
||||
} from '../types/import'
|
||||
|
||||
const RECENT_RESULTS_CAP = 25
|
||||
const emptyCounters = (): ImportCounters => ({ discovered: 0, processed: 0, created: 0, updated: 0, exists: 0, error: 0 })
|
||||
|
||||
export const useImportStore = defineStore('chronoImportStore', () => {
|
||||
|
||||
// State
|
||||
|
||||
const nextId = ref(0)
|
||||
const files = ref<ImportFileEntry[]>([])
|
||||
const stage = ref<ImportSessionStage>('idle')
|
||||
const running = ref(false)
|
||||
const sessions = ref<Record<number, ImportSession>>({})
|
||||
const order = ref<number[]>([])
|
||||
|
||||
// Computed
|
||||
|
||||
const totals = computed(() => order.value.reduce((total, id) => {
|
||||
const counters = sessions.value[id]?.counters
|
||||
if (counters) Object.keys(total).forEach(key => {
|
||||
total[key as keyof ImportCounters] += counters[key as keyof ImportCounters]
|
||||
})
|
||||
return total
|
||||
}, emptyCounters()))
|
||||
|
||||
// Actions
|
||||
|
||||
function addFile(file: ImportFileAdd): number {
|
||||
const id = nextId.value++
|
||||
files.value.push({
|
||||
file: { id, ...file },
|
||||
collectionId: null,
|
||||
options: { supersede: false }
|
||||
})
|
||||
return id
|
||||
}
|
||||
|
||||
function removeAllFiles() {
|
||||
files.value = []
|
||||
}
|
||||
|
||||
function reset() {
|
||||
stage.value = 'idle';
|
||||
running.value = false;
|
||||
sessions.value = {};
|
||||
order.value = [];
|
||||
}
|
||||
|
||||
function entryFor(id: number) {
|
||||
return files.value.find(entry => entry.file.id === id)
|
||||
}
|
||||
|
||||
/** Set the destination collection for a queued file. */
|
||||
function setCollectionForFile(id: number, collectionId: CollectionIdentifier | null) {
|
||||
const entry = entryFor(id);
|
||||
if (entry) entry.collectionId = collectionId;
|
||||
}
|
||||
|
||||
/** Merge option changes for a queued file without dropping untouched keys. */
|
||||
function setOptionsForFile(id: number, options: Partial<ImportFileOptions>) {
|
||||
const entry = entryFor(id);
|
||||
if (entry) entry.options = { ...entry.options, ...options };
|
||||
}
|
||||
|
||||
async function startImport(): Promise<ImportCounters> {
|
||||
const entries = [...files.value]
|
||||
sessions.value = {}
|
||||
order.value = entries.map(entry => entry.file.id)
|
||||
for (const entry of entries) sessions.value[entry.file.id] = {
|
||||
fileId: entry.file.id, fileName: entry.file.name, targetIdentifier: entry.collectionId,
|
||||
status: 'pending', counters: emptyCounters(), recentResults: [], lastError: null,
|
||||
}
|
||||
|
||||
running.value = true
|
||||
stage.value = 'importing'
|
||||
try {
|
||||
for (const entry of entries) {
|
||||
const session = sessions.value[entry.file.id]!
|
||||
if (!entry.collectionId) throw new Error(`No calendar selected for "${entry.file.name}"`)
|
||||
session.status = 'importing'
|
||||
try {
|
||||
await entityService.import(
|
||||
{ target: entry.collectionId, data: entry.file.contents, options: entry.options },
|
||||
(object: EntityImportResponse) => {
|
||||
session.counters.processed++
|
||||
session.counters[object.disposition]++
|
||||
session.recentResults = [object, ...session.recentResults].slice(0, RECENT_RESULTS_CAP)
|
||||
},
|
||||
expected => { session.counters.discovered = expected },
|
||||
)
|
||||
session.status = session.counters.error ? 'error' : 'completed'
|
||||
} catch (error) {
|
||||
session.status = 'error'
|
||||
session.lastError = error instanceof Error ? error.message : String(error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
stage.value = 'completed'
|
||||
return totals.value
|
||||
} catch (error) {
|
||||
stage.value = 'error'
|
||||
throw error
|
||||
} finally { running.value = false }
|
||||
}
|
||||
|
||||
return { files, stage, running, sessions, order, totals, addFile, removeAllFiles, reset, setCollectionForFile, setOptionsForFile, startImport }
|
||||
})
|
||||
Reference in New Issue
Block a user