diff --git a/apps/wizarr-frontend/src/components/WebhookList/WebhookItem.vue b/apps/wizarr-frontend/src/components/WebhookList/WebhookItem.vue index 6466f4f1a..0b2e4eae6 100644 --- a/apps/wizarr-frontend/src/components/WebhookList/WebhookItem.vue +++ b/apps/wizarr-frontend/src/components/WebhookList/WebhookItem.vue @@ -54,7 +54,6 @@ export default defineComponent({ if (await this.$modal.confirmModal(this.__("Are you sure?"), this.__("Are you sure you want to delete this webhook?"))) { this.disabled.delete = true; await this.deleteWebhook(this.webhook.id).finally(() => (this.disabled.delete = false)); - this.$toast.info(this.__("Webhook deleted successfully")); } else { this.$toast.info(this.__("Webhook deletion cancelled")); } diff --git a/apps/wizarr-frontend/src/stores/apikeys.ts b/apps/wizarr-frontend/src/stores/apikeys.ts index 2d5acc87a..60e27fc6d 100644 --- a/apps/wizarr-frontend/src/stores/apikeys.ts +++ b/apps/wizarr-frontend/src/stores/apikeys.ts @@ -1,69 +1,51 @@ +// Import the types for API keys and the Pinia library function for creating a store import type { APIKey, APIKeys } from '@/types/api/apikeys'; - import { defineStore } from 'pinia'; +// Define the shape of the state in this store interface APIKeyStoreState { apikeys: APIKeys; } +// Define and export a store named 'apikeys' using the Pinia library export const useAPIKeyStore = defineStore('apikeys', { + // Define the initial state of the store state: (): APIKeyStoreState => ({ apikeys: [], }), + // Define actions that can mutate the state actions: { + // Asynchronously fetches API keys from the server and updates the state async getAPIKeys() { - // Get the API keys from the API - const apikeys = await this.$axios + const response = await this.$axios .get('/api/apikeys') .catch(() => { this.$toast.error('Could not get API keys'); return null; }); - // If the API keys are null, return - if (apikeys === null) return; - - // Update the API keys that are already in the store - this.apikeys.forEach((apikey, index) => { - const new_apikey = apikeys.data.find( - (new_apikey: APIKey) => new_apikey.id === apikey.id, - ); - if (new_apikey) this.apikeys[index] = new_apikey; - }); - - // Add the new API keys to the store if they don't exist - apikeys.data.forEach((apikey: APIKey) => { - if ( - !this.apikeys.find( - (old_apikey) => old_apikey.id === apikey.id, - ) - ) - this.apikeys.push(apikey); - }); - - // Remove the API keys that were not in the response - this.apikeys.forEach((apikey, index) => { - if ( - !apikeys.data.find( - (new_apikey: APIKey) => new_apikey.id === apikey.id, - ) - ) - this.apikeys.splice(index, 1); + if (response !== null) { + this.updateAPIKeys(response.data); + } + }, + // Updates the current apikeys state with new data + updateAPIKeys(newAPIKeys: APIKeys) { + const newAPIKeyMap = new Map(newAPIKeys.map(key => [key.id, key])); + const updatedAPIKeys = this.apikeys.map(apikey => newAPIKeyMap.get(apikey.id) || apikey); + newAPIKeyMap.forEach((apikey, id) => { + if (!this.apikeys.some(k => k.id === id)) { + updatedAPIKeys.push(apikey); + } }); - - // Return the API keys - return apikeys.data; + this.apikeys = updatedAPIKeys.filter(apikey => newAPIKeyMap.has(apikey.id)); }, + // Creates a new API key on the server and updates the local state if successful async createAPIKey(apikey: Partial) { - // Convert the API key to a FormData object const formData = new FormData(); - Object.keys(apikey).forEach((key) => { // @ts-ignore formData.append(key, apikey[key]); }); - - // Create the API key const response = await this.$axios .post('/api/apikeys', formData, { disableErrorToast: true }) .catch((err) => { @@ -72,17 +54,13 @@ export const useAPIKeyStore = defineStore('apikeys', { return null; }); - // If the response is null, return - if (response === null) return; - - // Add the API key to the store - this.apikeys.push(response.data as APIKey); - - // Return the API key - return response.data as APIKey; + if (response !== null) { + this.apikeys.push(response.data as APIKey); + return response.data as APIKey; + } }, + // Deletes an API key from the server and removes it from the local state if successful async deleteAPIKey(id: number) { - // Delete the API key from the API const response = await this.$axios .delete(`/api/apikeys/${id}`, { disableInfoToast: true }) .catch(() => { @@ -90,15 +68,11 @@ export const useAPIKeyStore = defineStore('apikeys', { return null; }); - // If the response is null, return - if (response === null) return; - - // Remove the API key from the store - const index = this.apikeys.findIndex( - (apikey: APIKey) => apikey.id === id, - ); - if (index !== -1) this.apikeys.splice(index, 1); + if (response !== null) { + this.apikeys = this.apikeys.filter(apikey => apikey.id !== id); + } }, }, + // Persist the state of the store to local storage or another persistence layer persist: true, }); diff --git a/apps/wizarr-frontend/src/stores/invitations.ts b/apps/wizarr-frontend/src/stores/invitations.ts index 545a4c23d..d7c5eaa99 100644 --- a/apps/wizarr-frontend/src/stores/invitations.ts +++ b/apps/wizarr-frontend/src/stores/invitations.ts @@ -1,63 +1,46 @@ +// Import the types for invitations and the Pinia library function for creating a store import type { Invitation, Invitations } from '@/types/api/invitations'; - import { defineStore } from 'pinia'; +// Define the shape of the state in this store interface InvitationStoreState { - invitations: any[]; + invitations: Invitations; } +// Define and export a store named 'invitations' using the Pinia library export const useInvitationStore = defineStore('invitations', { + // Define the initial state of the store state: (): InvitationStoreState => ({ - invitations: [] as Invitations, + invitations: [], }), + // Define actions that can mutate the state actions: { + // Asynchronously fetches invitations from the server and updates the state async getInvitations() { - // Get invites from API - const invitations = await this.$axios + const response = await this.$axios .get('/api/invitations') .catch(() => { this.$toast.error('Could not get invitations'); return null; }); - // If the invites are null, return - if (invitations === null) return; - - // Update the invites that are already in the store - this.invitations.forEach((invite, index) => { - const new_invitation = invitations.data.find( - (new_invitation: Invitation) => - new_invitation.id === invite.id, - ); - if (new_invitation) this.invitations[index] = new_invitation; - }); - - // Add the new invites to the store if they don't exist - invitations.data.forEach((invitation: Invitation) => { - if ( - !this.invitations.find( - (old_invitation) => old_invitation.id === invitation.id, - ) - ) - this.invitations.push(invitation); - }); - - // Remove the invites that were not in the response - this.invitations.forEach((invitation, index) => { - if ( - !invitations.data.find( - (new_invitation: Invitation) => - new_invitation.id === invitation.id, - ) - ) - this.invitations.splice(index, 1); + if (response !== null) { + this.updateInvitations(response.data); + } + }, + // Updates the current invitations state with new data + updateInvitations(newInvitations: Invitations) { + const newInvitationMap = new Map(newInvitations.map(invite => [invite.id, invite])); + const updatedInvitations = this.invitations.map(invite => newInvitationMap.get(invite.id) || invite); + newInvitationMap.forEach((invite, id) => { + if (!this.invitations.some(i => i.id === id)) { + updatedInvitations.push(invite); + } }); - - // Return the invites - return invitations.data; + this.invitations = updatedInvitations.filter(invite => newInvitationMap.has(invite.id)); }, + // Creates a new invitation on the server and updates the local state if successful async createInvitation(invitation: FormData | Partial) { - // Create the invite const response = await this.$axios .post('/api/invitations', invitation, { disableErrorToast: true, @@ -68,17 +51,13 @@ export const useInvitationStore = defineStore('invitations', { return null; }); - // If the response is null, return - if (response === null) return; - - // Add the invite to the store - this.invitations.push(response.data as Invitation); - - // Return the invite - return response.data as Invitation; + if (response !== null) { + this.invitations.push(response.data as Invitation); + return response.data as Invitation; + } }, + // Deletes an invitation from the server and removes it from the local state if successful async deleteInvitation(id: number) { - // Delete the invite from the API const response = await this.$axios .delete(`/api/invitations/${id}`, { disableInfoToast: true }) .catch((err) => { @@ -87,15 +66,11 @@ export const useInvitationStore = defineStore('invitations', { return null; }); - // If the response is null, return - if (response === null) return; - - // Remove the invite from the store - const index = this.invitations.findIndex( - (invitation: Invitation) => invitation.id === id, - ); - if (index !== -1) this.invitations.splice(index, 1); + if (response !== null) { + this.invitations = this.invitations.filter(invitation => invitation.id !== id); + } }, }, + // Persist the state of the store to local storage or another persistence layer persist: true, }); diff --git a/apps/wizarr-frontend/src/stores/libraries.ts b/apps/wizarr-frontend/src/stores/libraries.ts index dc5c9ac2c..7374ae65f 100644 --- a/apps/wizarr-frontend/src/stores/libraries.ts +++ b/apps/wizarr-frontend/src/stores/libraries.ts @@ -1,111 +1,76 @@ import { defineStore } from 'pinia'; +// Define and export a Pinia store named 'libraries' export const useLibrariesStore = defineStore('libraries', { + // Define the state with initial structure for libraries state: () => ({ libraries: [] as Array<{ id: string; name: string; created: Date }>, }), + // Define actions that can mutate the state actions: { + // Asynchronously fetches libraries from the server and updates the state async getLibraries() { - // Get the libraries from the API - const response = await this.$axios.get('/api/libraries'); - - // Check if the response is valid - if (!response?.data) { - this.$toast.error('Could not get libraries'); - return; + try { + const response = await this.$axios.get('/api/libraries'); + if (!response?.data) { + throw new Error('Invalid response data'); // Throws an error if the data is not as expected + } + this.libraries = response.data.map(({ id, name, created }: { id: string; name: string; created: string }) => ({ + id, + name, + created: new Date(created) // Convert 'created' string to Date object + })); + } catch (error) { + this.$toast.error('Could not get libraries'); // Show error notification if the request fails } - - // Map the libraries to the correct format - this.libraries = response.data.map( - (library: { id: string; name: string; created: string }) => { - return { - id: library.id, - name: library.name, - created: new Date(library.created), - }; - }, - ); }, - async saveLibraries( - libraries: Array<{ id: string; name: string; selected: boolean }>, - ) { - const formData = new FormData(); - const newLibraries: string[] = []; - libraries.forEach((library) => { - if (library.selected) { - newLibraries.push(library.id); + // Saves selected libraries back to the server + async saveLibraries(libraries: Array<{ id: string; name: string; selected: boolean }>) { + try { + const selectedLibraries = libraries.filter(lib => lib.selected).map(lib => lib.id); + const formData = new FormData(); + formData.append('libraries', JSON.stringify(selectedLibraries)); + const response = await this.$axios.post('/api/libraries', formData, { disableInfoToast: true }); + if (!response?.data?.message) { + throw new Error('No success message in response'); // Check for a success message in the response } - }); - - formData.append('libraries', JSON.stringify(newLibraries)); - - const response = await this.$axios - .post('/api/libraries', formData, { disableInfoToast: true }) - .catch(() => { - return; - }); - - if (!response?.data?.message) { - this.$toast.error('Could not save libraries'); - return; + this.$toast.info('Successfully saved libraries'); // Notification for successful operation + } catch (error) { + this.$toast.error('Could not save libraries'); // Show error notification if the operation fails } - - this.$toast.info('Successfully saved libraries'); }, - async scanLibraries() { - // Get the libraries from the API - const libResponse = await this.$axios.get('/api/libraries'); - - // Check if the response is valid - if (!libResponse?.data) { - this.$toast.error('Could not get libraries'); - return; - } - - // Map the libraries to the correct format - const allLibraries = libResponse.data.map( - (library: { id: string; name: string; created: string }) => { - return { - id: library.id, - name: library.name, - created: new Date(library.created), - }; - }, - ) as Array<{ id: string; name: string; created: Date }>; - - // Update the libraries in the store - this.libraries = allLibraries; - // Get the libraries from the media server - const scanResponse = await this.$axios.get('/api/scan-libraries'); - - // Check if the response is valid - if (!scanResponse?.data?.libraries) { - this.$toast.error('Could not get libraries'); - return; - } - - // Map the libraries to the correct format - const libraries: [string, string][] = Object.entries( - scanResponse.data.libraries, - ); - const newLibraries: Array<{ - id: string; - name: string; - selected: boolean; - }> = []; + // Asynchronously fetches and synchronizes library data with a scan operation + async scanLibraries() { + try { + const [libResponse, scanResponse] = await Promise.all([ + this.$axios.get('/api/libraries'), // Get current libraries + this.$axios.get('/api/scan-libraries') // Perform a scanning operation + ]); + + if (!libResponse?.data || !scanResponse?.data?.libraries) { + throw new Error('Invalid response data'); // Validate responses + } - // Check if the library is selected - for (const [name, id] of libraries) { - const selected = - allLibraries.find((library) => library.id === id) !== - undefined; - newLibraries.push({ id: id, name: name, selected: selected }); + this.libraries = libResponse.data.map(({ id, name, created }: { id: string; name: string; created: string }) => ({ + id, + name, + created: new Date(created) // Update libraries state + })); + + const allLibrariesMap = new Map(this.libraries.map(lib => [lib.id, lib.name])); + const newLibraries = Object.entries(scanResponse.data.libraries).map(([name, id]) => ({ + id, + name, + selected: allLibrariesMap.has(id) // Mark if already present + })); + + return newLibraries; // Return new libraries for potential further processing + } catch (error) { + this.$toast.error('Could not get libraries'); // Error handling } - - return newLibraries; }, }, - persist: true, + persist: true, // Enable state persistence across sessions }); diff --git a/apps/wizarr-frontend/src/stores/requests.ts b/apps/wizarr-frontend/src/stores/requests.ts index 7b02470f3..197524c3c 100644 --- a/apps/wizarr-frontend/src/stores/requests.ts +++ b/apps/wizarr-frontend/src/stores/requests.ts @@ -1,79 +1,72 @@ import type { Request, Requests } from "@/types/api/request"; - import { defineStore } from "pinia"; +// Interface for the state shape of the store interface RequestsStoreState { requests: Requests; } +// Define and export a store for handling request data export const useRequestsStore = defineStore("requests", { + // Initial state setup for the store state: (): RequestsStoreState => ({ requests: [], }), + // Actions that can be called to manipulate the state actions: { + // Asynchronously fetches requests from the server and updates the store async getRequests() { - // Get the requests from the API - const requests = await this.$axios.get("/api/requests").catch((err) => { - this.$toast.error("Could not get requests"); - return null; - }); - - // If the requests are null, return - if (requests === null) return; + try { + const response = await this.$axios.get("/api/requests"); + const receivedRequests = response.data || []; - // Update the requests that are already in the store - this.requests.forEach((request, index) => { - const new_request = requests.data.find((new_request: Request) => new_request.id === request.id); - if (new_request) this.requests[index] = new_request; - }); + // Update or add new requests in the store + this.requests = receivedRequests.reduce((acc: Request[], request: Request) => { + const index = acc.findIndex(r => r.id === request.id); + if (index !== -1) { + acc[index] = request; // Update existing request + } else { + acc.push(request); // Add new request + } + return acc; + }, this.requests.slice()); // Use a slice to create a copy and avoid mutation during iteration - // Add the new requests to the store if they don't exist - requests.data.forEach((request: Request) => { - if (!this.requests.find((old_request) => old_request.id === request.id)) this.requests.push(request); - }); - - // Remove the requests that were not in the response - this.requests.forEach((request, index) => { - if (!requests.data.find((new_request: Request) => new_request.id === request.id)) this.requests.splice(index, 1); - }); + // Remove any requests that no longer exist in the backend + this.requests = this.requests.filter(r => receivedRequests.some(req => req.id === r.id)); + } catch (error) { + this.$toast.error("Could not get requests"); // Notify the user of failure to fetch requests + console.error(error); + } }, - async createRequest(request: Request) { - // Convert the request to a FormData object - const formData = new FormData(); - - Object.keys(request).forEach((key) => { - // @ts-ignore - formData.append(key, request[key]); - }); + // Asynchronously creates a new request on the server and adds it to the store + async createRequest(requestData: Request) { + try { + const formData = new FormData(); + Object.entries(requestData).forEach(([key, value]) => formData.append(key, value)); - // Create the request - const response = await this.$axios.post("/api/requests", formData, { disableErrorToast: true }).catch((err) => { - this.$toast.error("Could not create request"); - console.error(err); - return null; - }); - - // If the response is null, return - if (response === null) return; - - // Add the request to the store - this.requests.push(response.data as Request); - - // Return the request - return response.data as Request; + const response = await this.$axios.post("/api/requests", formData); + if (response.data) { + this.requests.push(response.data); // Add the newly created request to the store + return response.data; // Return the newly created request data + } + } catch (error) { + this.$toast.error("Could not create request"); // Notify the user of failure to create request + console.error(error); + } }, + // Asynchronously deletes a request from the server and removes it from the store async deleteRequest(id: number) { - // Delete the request from the API - await this.$axios.delete(`/api/requests/${id}`).catch((err) => { - this.$toast.error("Could not delete request"); - console.error(err); - return null; - }); - - // Remove the request from the store - const index = this.requests.findIndex((request: Request) => request.id === id); - if (index !== -1) this.requests.splice(index, 1); + try { + await this.$axios.delete(`/api/requests/${id}`); + const index = this.requests.findIndex(request => request.id === id); + if (index !== -1) { + this.requests.splice(index, 1); // Remove the request from the store if found + } + } catch (error) { + this.$toast.error("Could not delete request"); // Notify the user of failure to delete request + console.error(error); + } }, }, - persist: true, + persist: true, // Enable persistence for the store to maintain state across sessions }); diff --git a/apps/wizarr-frontend/src/stores/sessions.ts b/apps/wizarr-frontend/src/stores/sessions.ts index c311ee4c4..50becfd8d 100644 --- a/apps/wizarr-frontend/src/stores/sessions.ts +++ b/apps/wizarr-frontend/src/stores/sessions.ts @@ -1,74 +1,60 @@ import type { Session, Sessions } from '@/types/api/sessions'; import { defineStore } from 'pinia'; +// Interface defining the state structure for the sessions store interface SessionsStoreState { sessions: Sessions; } +// Define and export a store for handling session data export const useSessionsStore = defineStore('sessions', { + // Initial state setup for the store state: (): SessionsStoreState => ({ sessions: [], }), + // Actions that can be called to manipulate the state actions: { + // Asynchronously fetches session data from the server and updates the store async getSessions() { - // Get the sessions from the API - const sessions = await this.$axios - .get('/api/sessions') - .catch((err) => { - this.$toast.error('Could not get sessions'); - return null; - }); - - // If the sessions are null, return - if (sessions === null) return; - - // Update the sessions that are already in the store - this.sessions.forEach((session, index) => { - const new_session = sessions.data.find( - (new_session: Session) => new_session.id === session.id, - ); - if (new_session) this.sessions[index] = new_session; - }); - - // Add the new sessions to the store if they don't exist - sessions.data.forEach((session: Session) => { - if ( - !this.sessions.find( - (old_session) => old_session.id === session.id, - ) - ) - this.sessions.push(session); - }); - - // Remove the sessions that were not in the response - this.sessions.forEach((session, index) => { - if ( - !sessions.data.find( - (new_session: Session) => new_session.id === session.id, - ) - ) - this.sessions.splice(index, 1); - }); + try { + const response = await this.$axios.get('/api/sessions'); + if (!response.data) throw new Error('No data received'); + + // Create a map of new sessions for quick lookup + const newSessionsMap = new Map(response.data.map((session: Session) => [session.id, session])); + + // Update existing sessions and add new ones + this.sessions = this.sessions.reduce((updatedSessions: Session[], session) => { + if (newSessionsMap.has(session.id)) { + const newSession = newSessionsMap.get(session.id); + if (newSession) { + updatedSessions.push(newSession); + newSessionsMap.delete(session.id); // Remove updated session from the map to avoid duplication + } + } else { + updatedSessions.push(session); // Keep sessions that weren't updated + } + return updatedSessions; + }, []); + } catch (error) { + this.$toast.error('Could not get sessions'); // Notify the user of failure to fetch sessions + console.error(error); + } }, - async deleteSession(id: number) { - // Delete the session from the API - const response = await this.$axios - .delete(`/api/sessions/${id}`) - .catch((err) => { - this.$toast.error('Could not delete session'); - console.error(err); - return null; - }); - - // If the response is null, return - if (response === null) return; - // Remove the session from the store - const index = this.sessions.findIndex( - (session: Session) => session.id === id, - ); - if (index !== -1) this.sessions.splice(index, 1); + // Asynchronously deletes a session from the server and removes it from the store + async deleteSession(id: number) { + try { + await this.$axios.delete(`/api/sessions/${id}`); + const index = this.sessions.findIndex(session => session.id === id); + if (index !== -1) { + this.sessions.splice(index, 1); // Remove the session from the store if found + } + } catch (error) { + this.$toast.error('Could not delete session'); // Notify the user of failure to delete session + console.error(error); + } }, }, - persist: true, + persist: true, // Enable persistence for the store to maintain state across sessions }); diff --git a/apps/wizarr-frontend/src/stores/tasks.ts b/apps/wizarr-frontend/src/stores/tasks.ts index aa205d1c5..9dc288b5b 100644 --- a/apps/wizarr-frontend/src/stores/tasks.ts +++ b/apps/wizarr-frontend/src/stores/tasks.ts @@ -1,150 +1,88 @@ import { defineStore } from 'pinia'; import type { Job, JobList } from '@/types/Tasks'; +// Interface defining the state structure for the tasks store interface TasksStoreState { jobs: JobList; } +// Define and export a store for handling job tasks export const useTasksStore = defineStore('tasks', { + // Initial state setup for the store state: (): TasksStoreState => ({ jobs: [], }), + // Actions that can be called to manipulate the state actions: { - async getJobs() { - // Get the jobs from the API - const jobs = await this.$axios - .get('/api/scheduler/jobs') - .catch((err) => { - this.$toast.error('Could not get jobs'); - return null; + // Generic method to fetch, update, or delete jobs based on provided parameters + async fetchAndUpdateJobs(url: string, method: 'GET' | 'POST' | 'DELETE' = 'GET', payload?: any) { + try { + // Perform the API call using Axios with the provided method, URL, and payload + const response = await this.$axios({ + url, + method, + data: payload ? JSON.stringify(payload) : undefined, }); - - // If the jobs are null, return - if (jobs === null) return; - - // Update the jobs that are already in the store - this.jobs.forEach((job, index) => { - const new_job = jobs.data.find( - (new_job: Job) => new_job.id === job.id, - ); - if (new_job) this.jobs[index] = new_job; - }); - - // Add the new jobs to the store if they don't exist - jobs.data.forEach((job: Job) => { - if (!this.jobs.find((old_job: Job) => old_job.id === job.id)) - this.jobs.push(job); - }); - - // Remove the jobs that were not in the response - this.jobs.forEach((job, index) => { - if (!jobs.data.find((new_job: Job) => new_job.id === job.id)) - this.jobs.splice(index, 1); - }); - - // Return the jobs - return jobs.data as JobList; + const jobData: Job | Job[] = response.data; + if (!jobData) { + throw new Error('No job data received'); + } + + // Check if the response contains an array of jobs or a single job + if (Array.isArray(jobData)) { + // Replace all jobs with the new array from the response + this.jobs = jobData; + } else { + // Update an existing job or add a new one to the list + const index = this.jobs.findIndex(job => job.id === jobData.id); + if (index !== -1) { + this.jobs[index] = jobData; // Update the existing job + } else { + this.jobs.push(jobData); // Add the new job to the list + } + } + return jobData; // Return the job data for further processing + } catch (error) { + // Handle errors and log them + const errorMessage = (error as Error).message; + this.$toast.error(`Could not perform action on job: ${errorMessage}`); + console.error(error); + return null; + } }, - async getJob(id: string) { - // Get the job from the API - const job = await this.$axios - .get(`/api/scheduler/jobs/${id}`) - .catch((err) => { - this.$toast.error('Could not get job'); - console.error(err); - return null; - }); - // If the job is null, return - if (job === null) return; - - // Update the job in the store - const index = this.jobs.findIndex((job: Job) => job.id === id); - if (index !== -1) this.jobs[index] = job.data; - - // Return the job - return job.data as Job; + // Specific actions to interact with jobs through API calls + getJobs() { + return this.fetchAndUpdateJobs('/api/scheduler/jobs'); }, - async runJob(id: string) { - // Run the job - const job = await this.$axios - .post(`/api/scheduler/jobs/${id}/run`) - .catch((err) => { - this.$toast.error('Could not run job'); - console.error(err); - return null; - }); - - // If the job is null, return - if (job === null) return; - - // Update the job in the store - const index = this.jobs.findIndex((job: Job) => job.id === id); - if (index !== -1) this.jobs[index] = job.data; - // Return the job - return job.data as Job; + getJob(id: string) { + return this.fetchAndUpdateJobs(`/api/scheduler/jobs/${id}`); }, - async pauseJob(id: string) { - // Pause the job - const job = await this.$axios - .post(`/api/scheduler/jobs/${id}/pause`) - .catch((err) => { - this.$toast.error('Could not pause job'); - console.error(err); - return null; - }); - - // If the job is null, return - if (job === null) return; - - // Update the job in the store - const index = this.jobs.findIndex((job: Job) => job.id === id); - if (index !== -1) this.jobs[index] = job.data; - // Return the job - return job.data as Job; + runJob(id: string) { + return this.fetchAndUpdateJobs(`/api/scheduler/jobs/${id}/run`, 'POST'); }, - async resumeJob(id: string) { - // Resume the job - const job = await this.$axios - .post(`/api/scheduler/jobs/${id}/resume`) - .catch((err) => { - this.$toast.error('Could not resume job'); - console.error(err); - return null; - }); - - // If the job is null, return - if (job === null) return; - - // Update the job in the store - const index = this.jobs.findIndex((job: Job) => job.id === id); - if (index !== -1) this.jobs[index] = job.data; - // Return the job - return job.data as Job; + pauseJob(id: string) { + return this.fetchAndUpdateJobs(`/api/scheduler/jobs/${id}/pause`, 'POST'); }, - async deleteJob(id: string) { - // Delete the job - const job = await this.$axios - .delete(`/api/scheduler/jobs/${id}`) - .catch((err) => { - this.$toast.error('Could not delete job'); - console.error(err); - return null; - }); - - // If the job is null, return - if (job === null) return; - // Update the job in the store - const index = this.jobs.findIndex((job: Job) => job.id === id); - if (index !== -1) this.jobs.splice(index, 1); + resumeJob(id: string) { + return this.fetchAndUpdateJobs(`/api/scheduler/jobs/${id}/resume`, 'POST'); + }, - // Return the job - return job.data as Job; + // Deletes a job and removes it from the local state if successful + async deleteJob(id: string) { + const jobData = await this.fetchAndUpdateJobs(`/api/scheduler/jobs/${id}`, 'DELETE'); + if (jobData !== null) { + const index = this.jobs.findIndex(job => job.id === id); + if (index !== -1) { + this.jobs.splice(index, 1); // Remove the job from the list + } + } + return jobData; // Return the job data, which should be null after deletion }, }, - persist: true, + persist: true, // Enable persistence for the store to maintain state across sessions }); diff --git a/apps/wizarr-frontend/src/stores/users.ts b/apps/wizarr-frontend/src/stores/users.ts index b202a0e01..3135d405a 100644 --- a/apps/wizarr-frontend/src/stores/users.ts +++ b/apps/wizarr-frontend/src/stores/users.ts @@ -1,87 +1,64 @@ import type { User, Users } from '@/types/api/users'; - import { defineStore } from 'pinia'; +// Interface defining the state structure for the users store interface UserStoreState { users: Users; } +// Define and export a store for handling user data export const useUsersStore = defineStore('users', { + // Initial state setup for the store state: (): UserStoreState => ({ users: [], }), + // Actions that can be called to manipulate the state actions: { + // Asynchronously scans for new users and updates the list if successful async scanUsers() { - // Trigger the scan through the API - const response = await this.$axios - .get('/api/users/scan') - .catch(() => { - this.$toast.error('Could not scan users'); - return null; - }); - - // If the response is null, return - if (response === null) return; + const response = await this.$axios.get('/api/users/scan').catch(() => { + this.$toast.error('Could not scan users'); // Notify user of failure to scan + return null; + }); - // Trigger the get users function - await this.getUsers(); + if (response !== null) { + await this.getUsers(); // Refresh user list after a successful scan + } }, + // Fetches users from the server and updates the state async getUsers() { - // Get the users from the API - const users = await this.$axios - .get('/api/users') - .catch(() => { - this.$toast.error('Could not get users'); - return null; - }); - - // If the users are null, return - if (users === null) return; - - // Update the users that are already in the store - this.users.forEach((user, index) => { - const new_user = users.data.find( - (new_user: User) => new_user.id === user.id, - ); - if (new_user) this.users[index] = new_user; + const response = await this.$axios.get('/api/users').catch(() => { + this.$toast.error('Could not get users'); // Notify user of failure to fetch users + return null; }); - // Add the new users to the store if they don't exist - users.data.forEach((user: User) => { - if (!this.users.find((old_user) => old_user.id === user.id)) - this.users.push(user); - }); - - // Remove the users that were not in the response - this.users.forEach((user, index) => { - if ( - !users.data.find( - (new_user: User) => new_user.id === user.id, - ) - ) - this.users.splice(index, 1); + if (response !== null) { + this.updateUsers(response.data); // Update state with new user data + } + }, + // Updates user list in the state + updateUsers(newUsers: Users) { + const newUserMap = new Map(newUsers.map(user => [user.id, user])); // Map for quick lookup of users + // Update existing users and add new ones + const updatedUsers = this.users.map(user => newUserMap.get(user.id) || user); + newUserMap.forEach((user, id) => { + if (!this.users.some(u => u.id === id)) { + updatedUsers.push(user); // Add new users not already present + } }); - - // Return the users - return users.data; + this.users = updatedUsers.filter(user => newUserMap.has(user.id)); // Filter to remove any not returned in the latest fetch }, + // Deletes a user from the server and removes from the state async deleteUser(id: number) { - // Delete the user from the API - const response = await this.$axios - .delete(`/api/users/${id}`, { disableInfoToast: true }) - .catch(() => { - this.$toast.error('Could not delete user'); - return null; - }); - - // If the response is null, return - if (response === null) return; + const response = await this.$axios.delete(`/api/users/${id}`, { disableInfoToast: true }).catch(() => { + this.$toast.error('Could not delete user'); // Notify user of failure to delete + return null; + }); - // Remove the user from the store - const index = this.users.findIndex((user: User) => user.id === id); - if (index !== -1) this.users.splice(index, 1); + if (response !== null) { + this.users = this.users.filter(user => user.id !== id); // Remove user from state + } }, }, - getters: {}, - persist: true, + persist: true, // Enable persistence for the store to maintain state across sessions }); diff --git a/apps/wizarr-frontend/src/stores/webhooks.ts b/apps/wizarr-frontend/src/stores/webhooks.ts index 528923a7b..227e73c64 100644 --- a/apps/wizarr-frontend/src/stores/webhooks.ts +++ b/apps/wizarr-frontend/src/stores/webhooks.ts @@ -1,100 +1,72 @@ import type { Webhook, Webhooks } from '@/types/api/webhooks'; - import { defineStore } from 'pinia'; +// Interface defining the state structure for the webhook store interface WebhookStoreState { webhooks: Webhooks; } +// Define and export a store for handling webhook data export const useWebhookStore = defineStore('webhooks', { + // Initial state setup for the store state: (): WebhookStoreState => ({ webhooks: [], }), + // Actions that can be called to manipulate the state actions: { + // Fetches webhooks from the server and updates the state async getWebhooks() { - // Get webhooks from API - const webhooks = await this.$axios - .get('/api/webhooks') - .catch(() => { - this.$toast.error('Could not get webhooks'); - return null; - }); - - // If the webhooks are null, return - if (webhooks === null) return; + try { + const response = await this.$axios.get('/api/webhooks'); + if (!response.data) throw new Error('No data received'); - // Update the webhooks that are already in the store - this.webhooks.forEach((webhook, index) => { - const new_webhook = webhooks.data.find( - (new_webhook: Webhook) => new_webhook.id === webhook.id, - ); - if (new_webhook) this.webhooks[index] = new_webhook; - }); + // Create a map of new webhooks for quick lookup + const newWebhooks = new Map(response.data.map(webhook => [webhook.id, webhook])); - // Add the new webhooks to the store if they don't exist - webhooks.data.forEach((webhook: Webhook) => { - if ( - !this.webhooks.find( - (old_webhook) => old_webhook.id === webhook.id, - ) - ) - this.webhooks.push(webhook); - }); + // Update existing webhooks and filter out any that no longer exist + this.webhooks = this.webhooks.map(webhook => newWebhooks.get(webhook.id) || webhook) + .filter(webhook => newWebhooks.has(webhook.id)); - // Remove the webhooks that were not in the response - this.webhooks.forEach((webhook, index) => { - if ( - !webhooks.data.find( - (new_webhook: Webhook) => new_webhook.id === webhook.id, - ) - ) - this.webhooks.splice(index, 1); - }); + // Add new webhooks that are not already in the store + const existingIds = new Set(this.webhooks.map(webhook => webhook.id)); + response.data.forEach(webhook => { + if (!existingIds.has(webhook.id)) { + this.webhooks.push(webhook); + } + }); + } catch (error) { + this.$toast.error('Could not get webhooks'); // Notify user of failure to fetch webhooks + console.error(error); + } }, + // Creates a new webhook on the server and adds it to the store if successful async createWebhook(webhook: Partial) { - // Convert the webhook to a FormData object - const formData = new FormData(); - - Object.keys(webhook).forEach((key) => { - // @ts-ignore - formData.append(key, webhook[key]); - }); - - // Create the webhook - const response = await this.$axios - .post('/api/webhooks', formData, { disableErrorToast: true }) - .catch((err) => { - this.$toast.error('Could not create webhook'); - console.error(err); - return null; + try { + const formData = new FormData(); + Object.entries(webhook).forEach(([key, value]) => { + formData.append(key, value); }); - // If the response is null, return - if (response === null) return; - - // Add the webhook to the store - this.webhooks.push(response.data as Webhook); + const response = await this.$axios.post('/api/webhooks', formData); + if (!response.data) throw new Error('Webhook creation failed'); - // Return the webhook - return response.data as Webhook; + this.webhooks.push(response.data); // Add the new webhook to the state + return response.data; // Return the newly created webhook + } catch (error) { + this.$toast.error('Could not create webhook'); // Notify user of failure to create webhook + console.error(error); + } }, + // Deletes a webhook from the server and removes it from the state async deleteWebhook(id: number) { - // Delete the webhook from the API - const response = await this.$axios - .delete(`/api/webhooks/${id}`, { disableInfoToast: true }) - .catch(() => { - this.$toast.error('Could not delete webhook'); - return null; - }); - - // If the response is null, return - if (response === null) return; - - // Remove the webhook from the store - this.webhooks.forEach((webhook, index) => { - if (webhook.id === id) this.webhooks.splice(index, 1); - }); + try { + await this.$axios.delete(`/api/webhooks/${id}`); + this.webhooks = this.webhooks.filter(webhook => webhook.id !== id); // Remove the webhook from the state + } catch (error) { + this.$toast.error('Could not delete webhook'); // Notify user of failure to delete webhook + console.error(error); + } }, }, - persist: true, + persist: true, // Enable persistence for the store to maintain state across sessions });