refactor : simplify codebase and fix critical issues

Backend:
- Add MCP Serializer to centralize entity-to-array conversion (~300 lines deduped)
- Fix race condition in task/ticket number generation (SELECT FOR UPDATE + transaction)
- Add unique constraint on task (project_id, number) with migration
- Fix MIME type validation: use server-detected finfo instead of client-supplied type
- Add allowlist of permitted MIME types for uploads
- Fix TaskDocumentDownloadController: allow ROLE_CLIENT access, add priority:1
- Fix notification sent even when ticket status unchanged
- Remove redundant exception constructors
- Simplify services (BookStackApi double fetch, TokenEncryptor, GiteaApi)
- Consolidate duplicate checks in processors

Frontend:
- Fix useApi isHandlingUnauthorized scope (module-level to prevent double 401 redirect)
- Fix client-tickets toast key copy-paste bug
- Merge duplicated tasks service methods (getByProject + getByProjectArchived)
- Extract shared uploadWithRelation helper in task-documents service
- Extract formatFileSize utility from duplicated component code
- Extract status transition logic into useClientTicketHelpers composable
- Remove dead code (unused router, handleLogout, empty script blocks)
- Merge duplicate watchers and onMounted calls
- Normalize arrow functions to function declarations per convention

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-15 22:09:16 +01:00
parent a5144443a4
commit e4fc34b90f
52 changed files with 662 additions and 569 deletions

View File

@@ -154,7 +154,7 @@
/>
<TaskDocumentList
v-if="isEditing && task"
:documents="documents"
:documents="localDocuments"
:is-admin="isAdmin"
@preview="openPreview"
@delete="handleDeleteDocument"
@@ -164,7 +164,7 @@
<TaskDocumentPreview
:document="previewDoc"
:has-prev="previewIndex > 0"
:has-next="previewIndex < documents.length - 1"
:has-next="previewIndex < localDocuments.length - 1"
@close="previewDoc = null"
@prev="prevPreview"
@next="nextPreview"
@@ -396,6 +396,14 @@ watch(() => props.modelValue, async (open) => {
} catch {
clientTickets.value = []
}
if (props.task?.project?.giteaOwner && props.task?.project?.giteaRepo && !giteaUrl.value) {
try {
const settings = await getGiteaSettings()
giteaUrl.value = settings.url ?? ''
} catch {
// Gitea not available
}
}
}
})
@@ -405,17 +413,6 @@ watch(() => props.task, (task) => {
}
})
watch(() => props.modelValue, async (open) => {
if (open && props.task?.project?.giteaOwner && props.task?.project?.giteaRepo && !giteaUrl.value) {
try {
const settings = await getGiteaSettings()
giteaUrl.value = settings.url ?? ''
} catch {
// Gitea not available
}
}
})
const { create, update, remove } = useTaskService()
const { remove: removeDocument, getByTask: getDocumentsByTask } = useTaskDocumentService()
const clientTicketService = useClientTicketService()
@@ -440,7 +437,6 @@ function ticketStatusClass(status: string): string {
}
const localDocuments = ref<TaskDocument[]>([])
const documents = computed(() => localDocuments.value)
const previewDoc = ref<TaskDocument | null>(null)
// Sync documents from task prop when modal opens or task changes
@@ -455,7 +451,7 @@ async function refreshDocuments() {
const previewIndex = computed(() => {
if (!previewDoc.value) return -1
return documents.value.findIndex(d => d.id === previewDoc.value!.id)
return localDocuments.value.findIndex(d => d.id === previewDoc.value!.id)
})
function openPreview(doc: TaskDocument) {
@@ -464,13 +460,13 @@ function openPreview(doc: TaskDocument) {
function prevPreview() {
if (previewIndex.value > 0) {
previewDoc.value = documents.value[previewIndex.value - 1]
previewDoc.value = localDocuments.value[previewIndex.value - 1]
}
}
function nextPreview() {
if (previewIndex.value < documents.value.length - 1) {
previewDoc.value = documents.value[previewIndex.value + 1]
if (previewIndex.value < localDocuments.value.length - 1) {
previewDoc.value = localDocuments.value[previewIndex.value + 1]
}
}