Rabbi_Gerzi/frontend/rabbi_gerzi/src/stores/elements.ts

417 lines
10 KiB
TypeScript

import { ref, type Ref } from 'vue'
import { defineStore } from 'pinia'
export interface ChildElement {
id: number
title: string
description: string
}
export interface Element extends ChildElement {
iconImageUrl: string | null
richText: string
shortPdfPath: string | null
longPdfPath: string | null
youtubeUrl: string | null
}
type ElementFileType = 'iconImage' | 'shortPdf' | 'longPdf'
interface ElementResponse {
element: Element
childElements: ChildElement[]
siblingElements: ChildElement[]
}
export interface UpdateElementInput {
title: string
description: string
richText: string
youtubeUrl: string
}
export interface CreateChildElementInput {
title: string
}
interface UpdateElementResponse {
element: Element
}
interface ElementPatchInput {
title?: string
description?: string
iconImageUrl?: string
richText?: string
shortPdfPath?: string
longPdfPath?: string
youtubeUrl?: string
}
interface ErrorResponse {
error?: string
}
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL as string
const ELEMENTS_URL = `${API_BASE_URL}/api/elements`
const ELEMENT_UPDATE_URL = `${API_BASE_URL}/api/element/update`
export const useElementsStore = defineStore('elements', () => {
const element = ref<Element | null>(null)
const childElements = ref<ChildElement[]>([])
const siblingElements = ref<ChildElement[]>([])
const isLoading = ref(false)
const error = ref<string | null>(null)
const isSaving = ref(false)
const isUploadingIconImage = ref(false)
const isUploadingShortPdf = ref(false)
const isUploadingLongPdf = ref(false)
const isManagingChildren = ref(false)
const saveError = ref<string | null>(null)
const childActionError = ref<string | null>(null)
async function fetchElement(elementId: string): Promise<void> {
element.value = null
childElements.value = []
siblingElements.value = []
error.value = null
saveError.value = null
childActionError.value = null
isLoading.value = true
try {
const encodedElementId = encodeURIComponent(elementId)
const elementUrl = `${API_BASE_URL}/api/elements/${encodedElementId}`
const response = await fetch(elementUrl)
if (response.status === 404) {
error.value = 'Element not found'
return
}
if (!response.ok) {
error.value = 'Could not load element'
return
}
const data: ElementResponse = await response.json()
element.value = data.element
childElements.value = data.childElements
siblingElements.value = data.siblingElements
} catch {
childElements.value = []
siblingElements.value = []
error.value = 'Network error - could not load element'
} finally {
isLoading.value = false
}
}
async function updateElement(
elementId: string,
input: UpdateElementInput,
): Promise<boolean> {
return await saveElementPatch(elementId, input, 'Could not save element')
}
async function createChildElement(
parentElementId: string,
input: CreateChildElementInput,
): Promise<Element | null> {
childActionError.value = null
isManagingChildren.value = true
try {
const encodedParentElementId = encodeURIComponent(parentElementId)
const response = await fetch(
`${ELEMENTS_URL}/${encodedParentElementId}/children`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify(input),
},
)
return await handleChildElementResponse(
response,
'Could not add child element',
)
} catch {
childActionError.value = 'Network error - could not add child element'
return null
} finally {
isManagingChildren.value = false
}
}
async function deleteChildElement(
parentElementId: string,
childElementId: number,
): Promise<boolean> {
childActionError.value = null
isManagingChildren.value = true
try {
const encodedParentElementId = encodeURIComponent(parentElementId)
const encodedChildElementId = encodeURIComponent(
childElementId.toString(),
)
const response = await fetch(
`${ELEMENTS_URL}/${encodedParentElementId}` +
`/children/${encodedChildElementId}`,
{
method: 'DELETE',
credentials: 'include',
},
)
return await handleDeleteChildElementResponse(response, childElementId)
} catch {
childActionError.value = 'Network error - could not remove child element'
return false
} finally {
isManagingChildren.value = false
}
}
async function clearElementIconImage(elementId: string): Promise<boolean> {
return await saveElementPatch(
elementId,
{ iconImageUrl: '' },
'Could not remove icon image',
)
}
async function clearElementShortPdf(elementId: string): Promise<boolean> {
return await saveElementPatch(
elementId,
{ shortPdfPath: '' },
'Could not remove short PDF',
)
}
async function clearElementLongPdf(elementId: string): Promise<boolean> {
return await saveElementPatch(
elementId,
{ longPdfPath: '' },
'Could not remove long PDF',
)
}
async function saveElementPatch(
elementId: string,
input: ElementPatchInput,
failureMessage: string,
): Promise<boolean> {
saveError.value = null
isSaving.value = true
try {
const response = await fetch(ELEMENT_UPDATE_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ elementId, ...input }),
})
return await handleElementResponse(response, failureMessage)
} catch {
saveError.value = `Network error - ${failureMessage.toLowerCase()}`
return false
} finally {
isSaving.value = false
}
}
async function uploadElementIconImage(
elementId: string,
file: File,
): Promise<boolean> {
return await uploadElementFile(
elementId,
file,
'iconImage',
isUploadingIconImage,
'Could not upload icon image',
'Network error - could not upload icon image',
)
}
async function uploadElementShortPdf(
elementId: string,
file: File,
): Promise<boolean> {
return await uploadElementFile(
elementId,
file,
'shortPdf',
isUploadingShortPdf,
'Could not upload short PDF',
'Network error - could not upload short PDF',
)
}
async function uploadElementLongPdf(
elementId: string,
file: File,
): Promise<boolean> {
return await uploadElementFile(
elementId,
file,
'longPdf',
isUploadingLongPdf,
'Could not upload long PDF',
'Network error - could not upload long PDF',
)
}
async function uploadElementFile(
elementId: string,
file: File,
fileType: ElementFileType,
isUploadingFile: Ref<boolean>,
failureMessage: string,
networkErrorMessage: string,
): Promise<boolean> {
saveError.value = null
isUploadingFile.value = true
try {
const formData = new FormData()
formData.append('elementId', elementId)
formData.append('fileType', fileType)
formData.append('file', file)
const response = await fetch(ELEMENT_UPDATE_URL, {
method: 'POST',
headers: { Accept: 'application/json' },
credentials: 'include',
body: formData,
})
return await handleElementResponse(response, failureMessage)
} catch {
saveError.value = networkErrorMessage
return false
} finally {
isUploadingFile.value = false
}
}
async function handleElementResponse(
response: Response,
failureMessage: string,
): Promise<boolean> {
if (response.status === 401) {
saveError.value = 'Please log in again'
return false
}
if (response.status === 404) {
saveError.value = 'Element not found'
return false
}
if (response.status === 400) {
saveError.value = await errorMessage(response, failureMessage)
return false
}
if (!response.ok) {
saveError.value = failureMessage
return false
}
const data: UpdateElementResponse = await response.json()
element.value = data.element
return true
}
async function handleChildElementResponse(
response: Response,
failureMessage: string,
): Promise<Element | null> {
if (response.status === 401) {
childActionError.value = 'Please log in again'
return null
}
if (response.status === 400 || response.status === 404) {
childActionError.value = await errorMessage(response, failureMessage)
return null
}
if (!response.ok) {
childActionError.value = failureMessage
return null
}
const data: UpdateElementResponse = await response.json()
return data.element
}
async function handleDeleteChildElementResponse(
response: Response,
childElementId: number,
): Promise<boolean> {
if (response.status === 401) {
childActionError.value = 'Please log in again'
return false
}
if (response.status === 400 || response.status === 404) {
childActionError.value = await errorMessage(
response,
'Could not remove child element',
)
return false
}
if (!response.ok) {
childActionError.value = 'Could not remove child element'
return false
}
childElements.value = childElements.value.filter((childElement) => {
return childElement.id !== childElementId
})
return true
}
async function errorMessage(
response: Response,
fallbackMessage: string,
): Promise<string> {
try {
const data: ErrorResponse = await response.json()
return data.error ?? fallbackMessage
} catch {
return fallbackMessage
}
}
return {
element,
childElements,
siblingElements,
isLoading,
error,
isSaving,
isUploadingIconImage,
isUploadingShortPdf,
isUploadingLongPdf,
isManagingChildren,
saveError,
childActionError,
fetchElement,
updateElement,
createChildElement,
deleteChildElement,
uploadElementIconImage,
uploadElementShortPdf,
uploadElementLongPdf,
clearElementIconImage,
clearElementShortPdf,
clearElementLongPdf,
}
})