diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..6ecb490 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,44 @@ +# syntax = docker/dockerfile:1 + +# Adjust NODE_VERSION as desired +ARG NODE_VERSION=21.7.3 +FROM node:${NODE_VERSION}-slim as base + +LABEL fly_launch_runtime="Node.js" + +# Node.js app lives here +WORKDIR /app + +# Set production environment +ENV NODE_ENV="production" + + +# Throw-away build stage to reduce size of final image +FROM base as build + +# Install packages needed to build node modules +RUN apt-get update -qq && \ + apt-get install --no-install-recommends -y build-essential node-gyp pkg-config python-is-python3 + +# Install node modules +COPY --link package-lock.json package.json ./ +RUN npm ci + +# Copy application code +COPY --link . . + + +# Final stage for app image +FROM base + +# Copy built application +COPY --from=build /app /app + +# Setup sqlite3 on a separate volume +RUN mkdir -p /data +VOLUME /data + +# Start the server by default, this can be overwritten at runtime +EXPOSE 3000 +ENV DATABASE_URL="file:///data/sqlite.db" +CMD [ "node", "index.js" ] diff --git a/flake.nix b/flake.nix index ee141ab..40dbc44 100644 --- a/flake.nix +++ b/flake.nix @@ -39,6 +39,7 @@ nodePackages.typescript-language-server pgformatter hurl + flyctl ]; }; } diff --git a/fly.toml b/fly.toml new file mode 100644 index 0000000..ffc4359 --- /dev/null +++ b/fly.toml @@ -0,0 +1,24 @@ +# fly.toml app configuration file generated for unibackend on 2024-05-29T21:35:15+02:00 +# +# See https://fly.io/docs/reference/configuration/ for information about how to use this file. +# + +app = 'unibackend' +primary_region = 'ams' + +[build] + +[[mounts]] + source = 'data' + destination = '/data' + +[http_service] + internal_port = 3000 + force_https = true + auto_stop_machines = true + auto_start_machines = true + min_machines_running = 0 + processes = ['app'] + +[[vm]] + size = 'shared-cpu-1x' diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..d8416b0 --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,71 @@ +# Frontend + + +Es soll eine Webseite mit HTML, CSS und JavaScript erstellt werden. Die Webseite soll hierbei eine Finanzverwaltung mit doppelter Buchführung abbilden. + +**Anforderungen:** + +- **Inhalt:** + - Es sind zwei Unterseiten vorzusehen: + - Übersicht über alle Konten + - Liste der Buchungen eines Kontos + - Die Übersichtsseite enthält je Konto mindestens folgende Angaben: + - Name des Kontos + - Beschreibung des Kontos + - Kontonummer + - Aktuelle Summe + +Die Konten sollen hierbei hierarchisch angeordnet werden (beispielsweise Aufwendungen mit den Unterkonten Miete, Lebensmittel und Semesterbeitrag). +Die Übersicht soll nach Kontonummer sortiert sein. + +- - Die Buchungsliste für ein Konto enthält je Buchung mindestens folgende Angaben: + - Titel der Buchung + - Betrag der Buchung auf dem Konto + - Gegenkonto +- **Navigation:** + - Zwischen Übersichtsseite und Buchungslisten soll eine Navigation über Hyperlinks möglich sein. +- **Struktur:** + - Es gibt auf allen Seiten einen einheitlichen Header und Footer. +- **Stylesheets:** + - CSS-Stylesheets sollen als separate Datei eingebunden werden + - Für das Styling des Headers und Footers ist ein gemeinsames Stylesheet zu nutzen. + - Die aktuelle Summe ist grün bei positiven, rot bei negativen und schwarz beim Betrag null zu formatieren. + - Es werden keine CSS-Frameworks eingesetzt (Bootstrap o.Ä.) + - Die Darstellung auf Smartphones und Desktop-Geräten soll sich unterscheiden (Responsive Design). +- **Schnittstelle:** + - Für den Abruf der Daten soll die bereitgestellte API genutzt werden. + - Unter der URL, die ihr im ersten Labor erhaltet, sind die Endpunkte der API mit OpenAPI dokumentiert und können dort auch getestet werden. + - Liefert die Schnittstelle einen Fehler zurück, soll dieser angemessen behandelt werden (d.h. Anzeige einer für den Benutzer sichtbaren und verständlichen Fehlermeldung). +- **Bearbeitung:** + - Buchungen sollen erstellt, bearbeitet und gelöscht werden können. + - Konten sollen erstellt, bearbeitet und gelöscht werden können. +- **Mehrteilige Buchungen:** + - Buchungen sollen auch so erstellt werden können, dass in einer + Buchung mehr als zwei Konten aufgeführt sind. +- **Gesamtbuchungsübersicht:** + - Es ist eine zusätzliche Seite zu erstellen, welche die Buchungen + aller Konten enthält. +- **Sortieren und Filtern:** + - Auf der Buchungsübersicht und der Gesamtbuchungsübersicht ist eine Sortier- und Filterfunktion zu implementieren. + - Die Sortierung muss auf- und absteigend erfolgen können nach: + - Buchungsdatum + - Wertstellungsdatum + - Die Filterung muss erfolgen können nach: + - Datum oder Zeitraum (Datum von - bis) für + - Buchungsdatum + - Wertstellungsdatum + - Titel der Buchung + - case-insensitive (Groß-/Kleinschreibung ignorieren) + - auch Teilausdrücke finden (beispielsweise soll "Brot" den Eintrag "Abendbrot für Sonntag" finden) + - Der Filter soll direkt beim Tippen angewandt werden, ohne dass eine Bestätigung notwendig ist. + - Betrag der Buchung (größer als .../kleiner als ...) + +Sind mehrere Filter gleichzeitig aktiv, werden diese logisch und-verknüpft. + +- - Standardmäßig sollen nur Buchungen des laufenden Kalenderjahres absteigend sortiert nach Buchungsdatum angezeigt werden. +- **Paginierung:** + - Die Anzeige auf der Buchungsübersicht und der Gesamtbuchungsübersicht soll paginierbar sein, das heißt, es werden auf einer Seite nur eine bestimmte Menge (initial 10) an Buchungen angezeigt. + - Die Seitengröße soll hierbei auf 10, 20, 50, 100 und „Alle“ einstellbar sein. + - Sind weniger als 10 Einträge vorhanden, sollen die Einstellungen zur Paginierung (Seitenauswahl, Seitengröße) verborgen werden. +- **Echtzeitupdates:** + - Die Anwendung soll Änderungen bei den Buchungen über SSE empfangen und in Echtzeit anzeigen. Hierfür dient der Endpunkt /live des Backends. \ No newline at end of file diff --git a/frontend/assets/favicon.ico b/frontend/assets/favicon.ico new file mode 100644 index 0000000..693afc2 Binary files /dev/null and b/frontend/assets/favicon.ico differ diff --git a/frontend/html/account.html b/frontend/html/account.html new file mode 100644 index 0000000..fb9e86d --- /dev/null +++ b/frontend/html/account.html @@ -0,0 +1,66 @@ + + + + + + + + Financial Accounting Tool + + + + + + + +
+
+ Account-EditorGesamtübersicht +
+
+
+
Kontonummer:
+ +
+
+
Kontoname:
+ +
+
+
Beschreibung:
+ +
+
+
Kontoart:
+ +
+
+
Übergeordnetes Konto:
+ +
+
+ + + +
+
+ +
+ + diff --git a/frontend/html/detail.html b/frontend/html/detail.html new file mode 100644 index 0000000..6ce12f7 --- /dev/null +++ b/frontend/html/detail.html @@ -0,0 +1,107 @@ + + + + + + + + Financial Accounting Tool + + + + + + + +
+
+ DetailansichtGesamtübersicht +
+ +
+
+
+
+ Zeitraum von + + bis + + +
+
+ +
+
+ + +
+
+
+ + + + + + + + + + + + +
+ Buchungsdatum + ValutaTitelGegenkontoBetrag
+
+ +
+ +
+ + diff --git a/frontend/html/overview.html b/frontend/html/overview.html new file mode 100644 index 0000000..2de5fb1 --- /dev/null +++ b/frontend/html/overview.html @@ -0,0 +1,85 @@ + + + + + + + + Financial Accounting Tool + + + + + + + +
+
+ Buchhaltungs-Gesamtübersicht +
+ +
+
+
+ Zeitraum von + + bis + + +
+
+ +
+
+
+ + + + + + + + + +
+ Buchungsdatum + ValutaTitel
+
+ +
+ +
+ + diff --git a/frontend/html/transaction.html b/frontend/html/transaction.html new file mode 100644 index 0000000..8e3186f --- /dev/null +++ b/frontend/html/transaction.html @@ -0,0 +1,72 @@ + + + + + + + + Financial Accounting Tool + + + + + + + +
+
+ Transaktions-EditorGesamtübersicht +
+
+
+
Buchungsdatum:
+ +
+
+
Wertstellungsdatum (Valuta):
+ +
+
+
Buchungstitel:
+ +
+ + + + + + + + + + +
KontoBetragBuchungstext
+
+ + +
+
+ +
+ + diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..34e20ab --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,39 @@ + + + + + + + + Financial Accounting Tool + + + + + + + +
+
+ BuchhaltungGesamtübersicht +
+
+ +
+
+ +
+ + diff --git a/frontend/index.js b/frontend/index.js new file mode 100644 index 0000000..db511c6 --- /dev/null +++ b/frontend/index.js @@ -0,0 +1,44 @@ +import { fetchData } from "./js/api.js"; +import { createAccordion } from "./js/accordion.js"; + + +function start() { + fetchData("accounts").then((data) => { + const container = document.getElementById("accordion-container"); + container.innerHTML = ""; + + data = data.sort((a, b) => (a.number > b.number) ? 1 : ((b.number > a.number) ? -1 : 0)); + + data.forEach(element => { + const linkName = element.name; + + const childrenContainer = createChildrens(element.subaccounts, linkName); + createAccordion(container, element, childrenContainer, linkName); + }); + }); +} + +/** + * Funktion mit rekusiven Aufruf um verschachtelte Accordions zu erzeugen + * @param {array} subaccounts + * @param {string} linkName + * @returns + */ +function createChildrens(subaccounts, linkName) { + const childrenContainer = document.createElement("div"); + childrenContainer.setAttribute("class", "children-container"); + + if (subaccounts.length > 0) { + subaccounts.forEach(account => { + const linkHref = `${linkName}:${account.name}`; + const container = createChildrens(account.subaccounts, linkHref); + createAccordion(childrenContainer, account, container, linkHref); + }) + + + return childrenContainer; + } + return null; +} + +start(); \ No newline at end of file diff --git a/frontend/js/accordion.js b/frontend/js/accordion.js new file mode 100644 index 0000000..a89df18 --- /dev/null +++ b/frontend/js/accordion.js @@ -0,0 +1,109 @@ +const euroFormat = new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }); + +/** + * Erstellt ein Accordion an einem vorgegebenen Punkt, mit einem Inhalt der übergeben wird + * @param {DOMElement} rootElement + * @param {object} element + * @param {DOMElement} body + * @param {string} linkName + */ +export function createAccordion(rootElement, element, body, linkName) { + // accordion + const accordion = document.createElement("div"); + accordion.setAttribute("class", "accordion clickable"); + + if (body !== null) + accordion.setAttribute("class", "accordion clickable"); + else + accordion.setAttribute("class", "accordion"); + + // accordion Titel + const accordionTitle = document.createElement("div"); + accordionTitle.setAttribute("class", "accordion-title"); + + // accordion Titel Nummer + const numberText = document.createElement("div"); + numberText.innerHTML = element.number; + numberText.setAttribute("class", "title-number"); + accordionTitle.appendChild(numberText); + + // accordion Titel Name + const titleText = document.createElement("div"); + titleText.setAttribute("class", "title-name"); + + const titleLink = document.createElement("a"); + titleLink.innerHTML = element.name; + titleLink.setAttribute("class", "title-link"); + titleLink.href = `html/detail.html?account=${linkName}`; + //titleLink.target = "_blank"; + + titleText.appendChild(titleLink); + accordionTitle.appendChild(titleText); + + // accordion Titel Beschreibung + const descriptionText = document.createElement("div"); + descriptionText.innerHTML = element.description ? element.description : ""; + descriptionText.setAttribute("class", "title-description"); + accordionTitle.appendChild(descriptionText); + + // accordion Titel Wert + const balanceText = document.createElement("div"); + balanceText.innerHTML = euroFormat.format(element.balance); + + if (element.balance < 0) + balanceText.setAttribute("class", "title-balance negative-number"); + else if (element.balance > 0) + balanceText.setAttribute("class", "title-balance positive-number"); + else + balanceText.setAttribute("class", "title-balance neutral-number"); + + accordionTitle.appendChild(balanceText); + + // accordion Anzeige, ob Unterkonten vorhanden + const indicatorText = document.createElement("div"); + indicatorText.innerHTML = element.subaccounts.length > 0 ? "↓" : ""; + indicatorText.setAttribute("class", "title-indicator"); + accordionTitle.appendChild(indicatorText); + + // accordion onclick handler + accordionTitle.onclick = () => { + if (accordion.querySelector(".accordion-content")) { + closeAccordion(accordion); + } else { + const accordions = document.querySelectorAll(".accordion"); + //accordions.forEach((accordion) => closeAccordion(accordion)); + if (body !== null) openAccordion(accordion, body); + } + } + + accordion.appendChild(accordionTitle); + rootElement.appendChild(accordion); +} + +/** + * Öffnet das Accordion + * @param {DOMElement} accordion + * @param {DOMElement} body + */ +function openAccordion(accordion, body) { + // accordion Inhalt + const accordionContent = document.createElement("div"); + accordionContent.setAttribute("class", "accordion-content"); + accordionContent.appendChild(body); + + accordion.appendChild(accordionContent); + accordion.setAttribute("class", "accordion clickable open"); +}; + + +/** + * Schließt das Accordion + * @param {DOMElement} accordion + * @param {DOMElement} body + */ +function closeAccordion(accordion) { + accordion.setAttribute("class", "accordion clickable"); + const content = accordion.querySelector(".accordion-content"); + if (content) + content.remove(); +}; \ No newline at end of file diff --git a/frontend/js/account.js b/frontend/js/account.js new file mode 100644 index 0000000..394caf1 --- /dev/null +++ b/frontend/js/account.js @@ -0,0 +1,110 @@ +import { fetchData, postData, putData, deleteData } from "./api.js"; + +const urlParams = new URLSearchParams(window.location.search); +let accounts = []; +let editAccount = null; + +function start() { + fetchData("accounts").then((data) => { + extractAccounts(data, ""); + const selectElement = document.getElementById("account-select"); + + accounts.forEach(element => { + const option = document.createElement("option"); + option.text = `${element.id} - ${element.name}`; + option.value = element.value; + selectElement.append(option); + }); + + // Prüft ob ein Paramter vorhanden ist, wenn ja dann wird das Konto geladen und die Daten zum bearbeiten befüllt + if (urlParams.get('account')) { + fetchData(`accounts/${urlParams.get('account')}`).then((data) => { + document.getElementById("number").value = data.number; + document.getElementById("name").value = data.name; + document.getElementById("description").value = data.description ? data.description : ""; + document.getElementById("type").value = data.type; + document.getElementById("account-select").value = data.qualifiedName.replace(`:${data.name}`, ""); + console.log(data.qualifiedName.replace(`:${data.name}`, "")) + editAccount = data; + document.getElementById("submit").value = "speichern"; + document.getElementById("delete").style.display = "block"; + }); + } + }); +} + +/** + * Extrahiert die Kontodetails/namen und erstellt dazu ein neues Array + * @param {array} data + * @param {string} parentName + */ +function extractAccounts(data, parentName) { + data.forEach(element => { + let parentAccount = element.name; + if (parentName !== "") + parentAccount = `${parentName}:${parentAccount}` + accounts.push({ id: element.number, name: element.name, value: parentAccount }); + if (element.subaccounts.length > 0) { + extractAccounts(element.subaccounts, parentAccount); + } + }); +} + +/** + * Eventhandler um den Vorgang abzubrechen und zurück zu Startseite zu navigieren + */ +document.getElementById("cancel").addEventListener("click", () => { + window.location.href = "../index.html"; +}); + +/** + * Eventhandler um ein Konto zu löschen + */ +document.getElementById("delete").addEventListener("click", () => { + if (editAccount && editAccount.entries.length > 0) { + alert("Löschen nur möglich, wenn keine Buchungen stattgefunden haben und keine Unterkonten existieren"); + return; + } + deleteData(`accounts/${urlParams.get('account')}`).then((response) => { + if (response) + window.location.href = "../index.html"; + }); +}); + +/** + * Eventhandler um ein neues Konto anzulegen bzw. das Konto zu bearbeiten + */ +document.getElementById("submit").addEventListener("click", () => { + if (editAccount && editAccount.entries.length > 0) { + alert("Accountbearbeitung nur möglich, wenn keine Buchungen stattgefunden haben"); + return; + } + + const number = document.getElementById("number").value; + const name = document.getElementById("name").value; + const description = document.getElementById("description").value; + const type = document.getElementById("type").value; + const parentAccount = document.getElementById("account-select").value; + + const data = { + "number": Number.parseInt(number), + "name": name, + "description": description, + "type": type, + "parentAccount": parentAccount + } + + if (editAccount) { + putData(`accounts/${urlParams.get('account')}`, data).then((response) => { + if (response) + window.location.href = "../index.html"; + }); + } else { + postData("accounts", data).then((response) => { + if (response) + window.location.href = "../index.html"; + }); + } +}); + +start(); \ No newline at end of file diff --git a/frontend/js/api.js b/frontend/js/api.js new file mode 100644 index 0000000..91031af --- /dev/null +++ b/frontend/js/api.js @@ -0,0 +1,127 @@ +// API URL +// const apiUrl = 'http://149.222.51.77/da4c59b9-43c4-4144-ae39-742c6ba3ad50/api/v1/'; +const apiUrl = '/api/v1/'; + +//SSE URL +export const sseUrl = 'http://149.222.51.77/da4c59b9-43c4-4144-ae39-742c6ba3ad50/live'; + +export function fetchData(url) { + const uri = apiUrl + url; + return get(uri); +} + +export function postData(url, data) { + const uri = apiUrl + url; + return post(uri, data); +} + +export function putData(url, data) { + const uri = apiUrl + url; + return put(uri, data); +} + +// Make a GET request +async function get(url) { + const controller = new AbortController(); + const timeoutId = setTimeout(() => { alert("Verbindungsprobleme"); controller.abort(); }, 10000); + const response = await fetch(url, { signal: controller.signal }); + + if (response.ok) { + clearTimeout(timeoutId); + return response.json(); + } else { + clearTimeout(timeoutId); + if (response.status === 400) + alert("Ungültige Anfrage"); + else if (response.status === 404) + alert("Transaktion existiert nicht"); + else (response.status === 500) + alert("Unerwarteter Fehler"); + } +} + +// Make a POST request +async function post(url, data) { + const controller = new AbortController(); + const timeoutId = setTimeout(() => { alert("Verbindungsprobleme"); controller.abort(); }, 10000); + const response = await fetch(url, { + signal: controller.signal, + headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, + method: "POST", + body: JSON.stringify(data) + }); + + if (response.ok) { + clearTimeout(timeoutId); + alert("Gespeichert!"); + return true; + } else { + clearTimeout(timeoutId); + if (response.status === 400) + alert("Ungültige Anfrage"); + else if (response.status === 422) + alert("Nicht ausführbare Anfrage"); + else (response.status === 500) + alert("Unerwarteter Fehler"); + + return false; + } +} + +// Make a PUT request +async function put(url, data) { + const controller = new AbortController(); + const timeoutId = setTimeout(() => { alert("Verbindungsprobleme"); controller.abort(); }, 10000); + const response = await fetch(url, { + signal: controller.signal, + headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, + method: "PUT", + body: JSON.stringify(data) + }); + + if (response.ok) { + clearTimeout(timeoutId); + alert("Gespeichert!"); + return true; + } else { + clearTimeout(timeoutId); + if (response.status === 400) + alert("Ungültige Anfrage"); + else if (response.status === 404) + alert("Konto existiert nicht"); + else if (response.status === 422) + alert("Nicht ausführbare Anfrage"); + else (response.status === 500) + alert("Unerwarteter Fehler"); + + return false; + } +} + +// Make a DELETE request +export async function deleteData(url) { + const controller = new AbortController(); + const timeoutId = setTimeout(() => { alert("Verbindungsprobleme"); controller.abort(); }, 10000); + const response = await fetch(apiUrl + url, { + signal: controller.signal, + method: "DELETE" + }); + + if (response.ok) { + clearTimeout(timeoutId); + alert("Gelöscht!"); + return true; + } else { + clearTimeout(timeoutId); + if (response.status === 400) + alert("Ungültige Anfrage"); + else if (response.status === 404) + alert("Konto existiert nicht"); + else if (response.status === 422) + alert("Nicht ausführbare Anfrage"); + else (response.status === 500) + alert("Unerwarteter Fehler"); + + return false; + } +} diff --git a/frontend/js/detail.js b/frontend/js/detail.js new file mode 100644 index 0000000..a57578b --- /dev/null +++ b/frontend/js/detail.js @@ -0,0 +1,350 @@ +import { fetchData, deleteData, sseUrl } from "./api.js"; + +const urlParams = new URLSearchParams(window.location.search); +const euroFormat = new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }); +let backendEntries = []; +let entries = []; + +// Verbindung zum SSE-Endpunkt herstellen +const eventSource = new EventSource(sseUrl); + +// Auf Update reagieren +eventSource.addEventListener('update', () => { + console.log("refreshing...") + start(); +}); + +function start() { + fetchData(`accounts/${urlParams.get('account')}`).then((data) => { + const root = document.getElementById("detail-container"); + root.innerHTML = ""; + + const title = document.createElement("div"); + title.innerHTML = `${data.number} - ${data.name}` + title.setAttribute("class", "detail-title"); + root.append(title); + + createDescriptionTable(root, data); + + if (data.description) { + const description = document.createElement("div"); + description.innerHTML = data.description; + root.append(description); + } + + backendEntries = [...data.entries]; + entries = [...backendEntries]; + + if (entries.length > 0) { + document.getElementById("table-container").style.display = "flex"; + + //Standardmäßig soll absteigend nach Buchungsdatum sortiert werden + entries = entries.sort((a, b) => (a.postingDate < b.postingDate) ? 1 : ((b.postingDate < a.postingDate) ? -1 : 0)); + //Standardmäßig sollen nur Buchungen des laufenden Kalenderjahres angezeigt werden + entries = entries.filter(element => new Date(element.postingDate) >= new Date(new Date().getFullYear(), 0, 1) && new Date(element.valueDate) >= new Date(new Date().getFullYear(), 0, 1)); + document.getElementById("beginDate").value = `${new Date().getFullYear()}-01-01`; + + createEntryTable(entries); + } + + + if (entries.length < 10) + document.getElementById("pagination-div").style.display = "none"; + }); +} + +/** + * Erzeugt die Tabelle und errechnet die anzuzeigenden Einträge + * @param {array} data + */ +function createEntryTable(data) { + const rootElement = document.querySelector(".table tbody"); + rootElement.innerHTML = null; + + const bookingDate = document.getElementById("bookingDate"); + bookingDate.onclick = () => sortTable("postingDate", bookingDate.innerHTML, bookingDate); + const valuta = document.getElementById("valuta"); + valuta.onclick = () => sortTable("valueDate", valuta.innerHTML, valuta); + + const pageInfo = document.getElementById("page-info"); + const arrayEntriesStart = (pageInfo.innerHTML - 1) * getBookingsPerPage(); + const arrayEntriesEnd = (pageInfo.innerHTML) * getBookingsPerPage(); + data = data.slice(arrayEntriesStart, arrayEntriesEnd); + + data.forEach(element => { + rootElement.append(createEntryTableRow(element)); + }); +} + +/** + * Erzeugt die einzelnen Tabellenzeilen + * @param {object} entry + * @returns DOMElement + */ +function createEntryTableRow(entry) { + const tr = document.createElement("tr"); + tr.setAttribute("class", "entry-table-tr"); + + const bookingDate = document.createElement("td"); + bookingDate.setAttribute("class", "entry-table-td"); + bookingDate.innerHTML = new Date(entry.postingDate).toLocaleDateString("de-DE"); + tr.append(bookingDate); + + const valuta = document.createElement("td"); + valuta.setAttribute("class", "entry-table-td"); + valuta.innerHTML = new Date(entry.valueDate).toLocaleDateString("de-DE"); + tr.append(valuta); + + const title = document.createElement("td"); + title.setAttribute("class", "entry-table-td"); + title.innerHTML = entry.title; + tr.append(title); + + const offsetAccount = document.createElement("td"); + offsetAccount.setAttribute("class", "entry-table-td"); + offsetAccount.innerHTML = entry.offsetAccounts; + tr.append(offsetAccount); + + const amount = document.createElement("td"); + amount.setAttribute("class", "entry-table-td"); + amount.innerHTML = euroFormat.format(entry.amount); + tr.append(amount); + console.log(entry) + + const action = document.createElement("td"); + action.setAttribute("class", "entry-table-td"); + action.style.textAlign="center"; + const editBtn = document.createElement("input"); + editBtn.setAttribute("class", "action-button-edit"); + editBtn.type = "button"; + editBtn.value = "edit"; + editBtn.onclick = () => window.location.href = `transaction.html?account=${urlParams.get('account')}&id=${entry.id}`; + action.append(editBtn); + + const deleteBtn = document.createElement("input"); + deleteBtn.setAttribute("class", "action-button"); + deleteBtn.type = "button"; + deleteBtn.value = "x"; + deleteBtn.onclick = () => deleteTransaction(entry); + action.append(deleteBtn); + + tr.append(action); + + return tr; +} + +/** + * triggert das löschen der Transaktion + * @param {object} transaction + */ +function deleteTransaction(transaction) { + deleteData(`transactions/${transaction.id}`).then(response => { + if(response) + window.location.reload(); + }) +} + +/** + * navigiert zur Seite, zum anlegen neuer Transaktionen + */ +document.getElementById("add-transaction").addEventListener("click", () => { + window.location.href = `transaction.html?account=${urlParams.get('account')}` +}); + +/** + * Erstellt die Detailtabelle + * @param {DOMElement} rootElement + * @param {object} data + */ +function createDescriptionTable(rootElement, data) { + const table = document.createElement("table"); + table.setAttribute("class", "description-table"); + table.setAttribute("border", "1px"); + + if (data.description) + table.append(createDescriptionTableRow("Kontoart:", data.type)); + table.append(createDescriptionTableRow("Bilanz (mit Unterkonten):", euroFormat.format(data.balance))); + table.append(createDescriptionTableRow("Bilanz (ohne Unterkonten):", euroFormat.format(data.localBalance))); + table.append(createDescriptionTableRow("Aktionen", `EDIT`)); + + + rootElement.append(table); +} + +/** + * Erzeugt die einzelnen Tabellenzeilen + * @param {string} title + * @param {string} value + * @returns + */ +function createDescriptionTableRow(title, value) { + const accounttype = document.createElement("tr"); + accounttype.setAttribute("class", "description-table-tr"); + + const accounttypeHeader = document.createElement("th"); + accounttypeHeader.setAttribute("class", "description-table-th"); + accounttypeHeader.innerHTML = title; + + const accounttypeValue = document.createElement("td"); + accounttypeValue.setAttribute("class", "description-table-td"); + accounttypeValue.innerHTML = value; + + accounttype.append(accounttypeHeader); + accounttype.append(accounttypeValue); + + return accounttype; +} + +/** + * Eventhandler für das sortieren der Tabellen Datumsspalten + * Managed die Pfeile zur Sortierrichtung und sortiert das entries array + * @param {string} column + * @param {string} columnName + * @param {DOMElement} docElement + */ +function sortTable(column, columnName, docElement) { + document.querySelectorAll(".clickable").forEach(element => { + if (element.innerHTML.slice(-1) === "↓" || element.innerHTML.slice(-1) === "↑") + element.innerHTML = element.innerHTML.slice(0, -1); + }); + + if (columnName.slice(-1) === "↓") { // ASC + docElement.innerHTML = `${docElement.innerHTML.slice(0, columnName.length - 1)}↑`; + entries = entries.sort((a, b) => (a[column] > b[column]) ? 1 : ((b[column] > a[column]) ? -1 : 0)); + } else if (columnName.slice(-1) === "↑") { // default + docElement.innerHTML = `${docElement.innerHTML.slice(0, columnName.length - 1)}`; + entries = backendEntries; + } else { // DESC + docElement.innerHTML = `${docElement.innerHTML} ↓`; + entries = entries.sort((a, b) => (a[column] < b[column]) ? 1 : ((b[column] < a[column]) ? -1 : 0)); + } + + createEntryTable(entries); +} +// Fügt den Eventhandler für die Filter Eingabefelder hinzu +document.getElementsByName("dateFilter").forEach(element => element.addEventListener("change", filterAll)); +document.getElementsByName("textFilter").forEach(element => element.addEventListener("keyup", filterAll)); + +// Eventhandler um die Datumsfelder zurückzusetzen +document.getElementById("resetDates").addEventListener("click", () => { + document.getElementById("beginDate").value = ""; + document.getElementById("endDate").value = ""; + filterAll(); +}); + +/** + * Filtert die Daten aus dem Backend nach den Filterfeldern + */ +function filterAll() { + let filteredEntries = backendEntries; + filteredEntries = filterDates(filteredEntries); + filteredEntries = filterText(filteredEntries); + filteredEntries = filterAmount(filteredEntries); + + createEntryTable(filteredEntries); +} + +/** + * Filtert die Daten anhand der Datumsfelder + * @param {array} array + * @returns array + */ +function filterDates(array) { + const begin = document.getElementById("beginDate").value; + const end = document.getElementById("endDate").value; + + if (begin !== "" && end !== "") { + return array.filter(element => (new Date(element.postingDate) >= new Date(begin) && new Date(element.valueDate) >= new Date(begin)) && (new Date(element.postingDate) <= new Date(end) && new Date(element.valueDate) <= new Date(end))); + } else if (begin !== "") { + return array.filter(element => new Date(element.postingDate) >= new Date(begin) && new Date(element.valueDate) >= new Date(begin)); + } else if (end !== "") { + return array.filter(element => new Date(element.postingDate) <= new Date(end) && new Date(element.valueDate) <= new Date(end)); + } + + return array; +} + +/** + * Filtert die Daten anhand der des Freitextfeldes + * @param {array} array + * @returns array + */ +function filterText(array) { + const searchString = document.getElementById("searchText").value; + if (searchString !== "") { + return array.filter(element => element.title.toLowerCase().includes(searchString.toLowerCase())); + } + + return array; +} + +/** + * Filtert die Daten anhand der des min. und max. Betrags + * @param {array} array + * @returns array + */ +function filterAmount(array) { + const begin = document.getElementById("beginAmount").value; + const end = document.getElementById("endAmount").value; + + if (begin !== "" && end !== "") { + return array.filter(element => (element.amount >= begin) && (element.amount <= end)); + } else if (begin !== "") { + return array.filter(element => element.amount >= begin); + } else if (end !== "") { + return array.filter(element => element.amount <= end); + } + + return array; +} + +/** + * Eventhandler für Pagination "Seite Vorwärts Button" + */ +document.getElementById("forward-page").addEventListener("click", () => { + const pageInfo = document.getElementById("page-info"); + let pageNumber = ++pageInfo.innerHTML; + const maxPage = Math.ceil(entries.length / getBookingsPerPage()); + + if (pageNumber > maxPage) + pageNumber = 1; + + pageInfo.innerHTML = pageNumber; + createEntryTable(entries); +}); + +/** + * Eventhandler für Pagination "Seite Rückwärts Button" + */ +document.getElementById("backward-page").addEventListener("click", () => { + const pageInfo = document.getElementById("page-info"); + let pageNumber = --pageInfo.innerHTML; + const maxPage = Math.ceil(entries.length / getBookingsPerPage()); + + if (pageNumber < 1) + pageNumber = maxPage; + + pageInfo.innerHTML = pageNumber; + createEntryTable(entries); +}); + +/** + * Eventhandler für Selectbox Änderung + */ +document.getElementById("table-range").addEventListener("change", () => { + document.getElementById("page-info").innerHTML = 1; //reset page + createEntryTable(entries); +}); + +/** + * Liefert die Anzahl der Einträge aus der Selectbox zurück bzw. die Array länge, bei Auswahl von "Alle" + * @returns integer + */ +function getBookingsPerPage() { + if (document.getElementById("table-range").value === "Alle") + return entries.length; + + return Number.parseInt(document.getElementById("table-range").value); +} + +start(); \ No newline at end of file diff --git a/frontend/js/overview.js b/frontend/js/overview.js new file mode 100644 index 0000000..160c830 --- /dev/null +++ b/frontend/js/overview.js @@ -0,0 +1,214 @@ +import { fetchData, sseUrl } from "./api.js"; + +let backendEntries = []; +let entries = []; + +// Verbindung zum SSE-Endpunkt herstellen +const eventSource = new EventSource(sseUrl); + +// Auf Update reagieren +eventSource.addEventListener('update', () => { + console.log("refreshing...") + start(); +}); + +function start() { + fetchData(`transactions`).then((data) => { + backendEntries = [...data]; + entries = [...backendEntries]; + + if (entries.length > 0) { + document.getElementById("table-container").style.display = "flex"; + + createTableBody(entries); + } + + if (entries.length < 10) + document.getElementById("pagination-div").style.display = "none"; + }); +} + +/** + * Generiert den Tabelleninhalt + * Anhand der angaben aus Seitenzahl und Zeilenangaben werden entsprechend viele Zeilen erstellt + * @param {array} data + */ +function createTableBody(data) { + const rootElement = document.querySelector(".table tbody"); + rootElement.innerHTML = null; + + const bookingDate = document.getElementById("bookingDate"); + bookingDate.onclick = () => sortTable("postingDate", bookingDate.innerHTML, bookingDate); + const valuta = document.getElementById("valuta"); + valuta.onclick = () => sortTable("valueDate", valuta.innerHTML, valuta); + + const pageInfo = document.getElementById("page-info"); + const arrayEntriesStart = (pageInfo.innerHTML-1) * getBookingsPerPage(); + const arrayEntriesEnd = (pageInfo.innerHTML) * getBookingsPerPage(); + data = data.slice(arrayEntriesStart, arrayEntriesEnd); + + data.forEach(element => { + rootElement.append(createEntryTableRow(element)); + }); +} + +/** + * Erzeugt die einzelne Tabellenzeile + * @param {object} entry + * @returns DOMElement + */ +function createEntryTableRow(entry) { + const tr = document.createElement("tr"); + tr.setAttribute("class", "entry-table-tr"); + + const bookingDate = document.createElement("td"); + bookingDate.setAttribute("class", "entry-table-td"); + bookingDate.innerHTML = new Date(entry.postingDate).toLocaleDateString("de-DE"); + tr.append(bookingDate); + + const valuta = document.createElement("td"); + valuta.setAttribute("class", "entry-table-td"); + valuta.innerHTML = new Date(entry.valueDate).toLocaleDateString("de-DE"); + tr.append(valuta); + + const title = document.createElement("td"); + title.setAttribute("class", "entry-table-td"); + title.innerHTML = entry.title; + tr.append(title); + + return tr; +} + +/** + * Eventhandler für das sortieren der Tabellen Datumsspalten + * Managed die Pfeile zur Sortierrichtung und sortiert das entries array + * @param {string} column + * @param {string} columnName + * @param {DOMElement} docElement + */ +function sortTable(column, columnName, docElement) { + document.querySelectorAll(".clickable").forEach(element => { + if(element.innerHTML.slice(-1) === "↓" || element.innerHTML.slice(-1) === "↑") + element.innerHTML = element.innerHTML.slice(0, -1); + }); + + if (columnName.slice(-1) === "↓") { // ASC + docElement.innerHTML = `${columnName.slice(0, columnName.length - 1)}↑`; + entries = entries.sort((a, b) => (a[column] > b[column]) ? 1 : ((b[column] > a[column]) ? -1 : 0)); + } else if (columnName.slice(-1) === "↑") { // default + docElement.innerHTML = `${columnName.slice(0, columnName.length - 1)}`; + entries = backendEntries; + } else { // DESC + docElement.innerHTML = `${columnName} ↓`; + entries = entries.sort((a, b) => (a[column] < b[column]) ? 1 : ((b[column] < a[column]) ? -1 : 0)); + } + + createTableBody(entries); +} + +// Fügt den Eventhandler für die Filter Eingabefelder hinzu +document.getElementsByName("dateFilter").forEach(element => element.addEventListener("change", filterAll)); +document.getElementsByName("textFilter").forEach(element => element.addEventListener("keyup", filterAll)); + +// Eventhandler um die Datumsfelder zurückzusetzen +document.getElementById("resetDates").addEventListener("click", () => { + document.getElementById("beginDate").value = ""; + document.getElementById("endDate").value = ""; + filterAll(); +}); + +/** + * Filtert die Daten aus dem Backend nach den Filterfeldern + */ +function filterAll() { + let filteredEntries = backendEntries; + filteredEntries = filterDates(filteredEntries); + filteredEntries = filterText(filteredEntries); + + createTableBody(filteredEntries); +} + +/** + * Filtert die Daten anhand der Datumsfelder + * @param {array} array + * @returns array + */ +function filterDates(array) { + const begin = document.getElementById("beginDate").value; + const end = document.getElementById("endDate").value; + + if (begin !== "" && end !== "") { + return array.filter(element => (new Date(element.postingDate) >= new Date(begin) && new Date(element.valueDate) >= new Date(begin)) && (new Date(element.postingDate) <= new Date(end) && new Date(element.valueDate) <= new Date(end))); + } else if (begin !== "") { + return array.filter(element => new Date(element.postingDate) >= new Date(begin) && new Date(element.valueDate) >= new Date(begin)); + } else if (end !== "") { + return array.filter(element => new Date(element.postingDate) <= new Date(end) && new Date(element.valueDate) <= new Date(end)); + } + + return array; +} + +/** + * Filtert die Daten anhand der des Freitextfeldes + * @param {array} array + * @returns array + */ +function filterText(array) { + const searchString = document.getElementById("searchText").value; + if (searchString !== "") { + return array.filter(element => element.title.toLowerCase().includes(searchString.toLowerCase())); + } + + return array; +} + +/** + * Eventhandler für Pagination "Seite Vorwärts Button" + */ +document.getElementById("forward-page").addEventListener("click", () => { + const pageInfo = document.getElementById("page-info"); + let pageNumber = ++pageInfo.innerHTML; + const maxPage = Math.ceil(entries.length / getBookingsPerPage()); + + if(pageNumber > maxPage) + pageNumber = 1; + + pageInfo.innerHTML = pageNumber; + createTableBody(entries); +}); + +/** + * Eventhandler für Pagination "Seite Rückwärts Button" + */ +document.getElementById("backward-page").addEventListener("click", () => { + const pageInfo = document.getElementById("page-info"); + let pageNumber = --pageInfo.innerHTML; + const maxPage = Math.ceil(entries.length / getBookingsPerPage()); + + if(pageNumber < 1) + pageNumber = maxPage; + + pageInfo.innerHTML = pageNumber; + createTableBody(entries); +}); + +/** + * Eventhandler für Selectbox Änderung + */ +document.getElementById("table-range").addEventListener("change", () => { + document.getElementById("page-info").innerHTML = 1; //reset page + createTableBody(entries); +}); + +/** + * Liefert die Anzahl der Einträge aus der Selectbox zurück bzw. die Array länge, bei Auswahl von "Alle" + * @returns integer + */ +function getBookingsPerPage() { + if(document.getElementById("table-range").value === "Alle") + return entries.length; + + return Number.parseInt(document.getElementById("table-range").value); +} + +start(); \ No newline at end of file diff --git a/frontend/js/transaction.js b/frontend/js/transaction.js new file mode 100644 index 0000000..da4d31a --- /dev/null +++ b/frontend/js/transaction.js @@ -0,0 +1,178 @@ +import { fetchData, postData, putData } from "./api.js"; + +const urlParams = new URLSearchParams(window.location.search); +let accounts = []; +let account = null; +let transaction = null; + +function start() { + fetchData("accounts").then((data) => { + extractAccounts(data, ""); + + if (urlParams.get('account')) { + fetchData(`accounts/${urlParams.get('account')}`).then((data) => { + account = data; + + if (urlParams.get('id')) { + fetchData(`transactions/${urlParams.get('id')}`).then((transactionData) => { + transaction = transactionData; + console.log(transaction) + + document.getElementById("submit").value = "bearbeiten"; + document.getElementById("postingDate").value = transaction.postingDate; + document.getElementById("valueDate").value = transaction.valueDate; + document.getElementById("title").value = transaction.title; + + transaction.entries.forEach(entry => { + addTableRow(entry); + }); + }); + } + }); + } + }); +} + +/** + * extract account hierarchy and create arraylist of all accounts + * @param {array} data + * @param {string} parentName + */ +function extractAccounts(data, parentName) { + data.forEach(element => { + let parentAccount = element.name; + if (parentName !== "") + parentAccount = `${parentName}:${parentAccount}` + accounts.push({ id: element.number, name: element.name, value: parentAccount }); + if (element.subaccounts.length > 0) { + extractAccounts(element.subaccounts, parentAccount); + } + }); +} + /** + * Eventhandler um Tabellenzeilen mit leeren Textfeldern hinzuzufügen + */ +document.getElementById("add-transaction").addEventListener("click", () => { + addTableRow(); +}); + +/** + * Fügt eine neue Tabellenzeile mit leeren Textfeldern hinzu + * @param {array} data + */ +function addTableRow(data) { + const table = document.querySelector(".transaction-table tbody"); + const tr = document.createElement("tr"); + tr.className = "transaction-tr"; + + const accountTd = document.createElement("td"); + accountTd.className = "transaction-td"; + const accountSelect = createAccountSelect() + accountTd.appendChild(accountSelect); + + const amountTd = document.createElement("td"); + amountTd.className = "transaction-td"; + const amountInput = document.createElement("input"); + amountInput.className = "transaction-input"; + amountInput.type = "number"; + amountInput.name = "amount"; + amountTd.appendChild(amountInput); + + const bookingTextTd = document.createElement("td"); + bookingTextTd.className = "transaction-td"; + const bookingInput = document.createElement("input"); + bookingInput.className = "transaction-input"; + bookingInput.name = "text"; + bookingTextTd.appendChild(bookingInput); + + const actionTd = document.createElement("td"); + actionTd.className = "transaction-td"; + const actionButton = document.createElement("input"); + actionButton.className = "action-button"; + actionButton.type = "button"; + actionButton.value = "x"; + actionButton.onclick = () => tr.remove(); + actionTd.appendChild(actionButton); + + if(data) { + accountSelect.value = data.account; + amountInput.value = data.amount; + bookingInput.value = data.label; + } + + tr.appendChild(accountTd); + tr.appendChild(amountTd); + tr.appendChild(bookingTextTd); + tr.appendChild(actionTd); + table.append(tr); +} + +/** + * Erstellt die Selectbox für Kontenauswahl + * @returns DOMElement + */ +function createAccountSelect() { + const selectElement = document.createElement("select"); + selectElement.className = "transaction-input"; + selectElement.name = "account"; + + const option = document.createElement("option"); + option.text = "--- Konto wählen ---"; + option.disabled = true; + option.selected = true; + selectElement.append(option); + + accounts.forEach(element => { + const option = document.createElement("option"); + option.text = `${element.id} - ${element.value}`; + option.value = element.value; + selectElement.append(option); + }); + + return selectElement; +} + +/** + * Eventhandler für das abbrechen und zurücknavigieren zur Homepage + */ +document.getElementById("cancel").addEventListener("click", () => { + window.location.href = "../index.html"; +}); + +/** + * Eventhandeler zum senden der Transaktionsdaten + * Liest die Werte aus den Eingabefenstern, erzeugt das Objekt und sendet sie zur API + */ +document.getElementById("submit").addEventListener("click", () => { + const postingDate = document.getElementById("postingDate").value; + const valueDate = document.getElementById("valueDate").value; + const title = document.getElementById("title").value; + + const data = { + "postingDate": postingDate, + "valueDate": valueDate, + "title": title, + "entries": [] + } + + document.querySelectorAll(".transaction-table tbody tr").forEach(tr => { + const account = tr.querySelector("[name=account]").value; + const amount = Number.parseFloat(tr.querySelector("[name=amount]").value); + const text = tr.querySelector("[name=text]").value; + data.entries.push({ account: account, amount: amount, label: text }); + }); + + if(transaction) { + putData(`transactions/${urlParams.get('id')}`, data).then((response) => { + if (response) + window.location.href = `./detail.html?account=${urlParams.get('account')}`; + }); + } else { + postData("transactions", data).then((response) => { + if (response) + window.location.href = `./detail.html?account=${urlParams.get('account')}`; + }); + } +}); + +start(); \ No newline at end of file diff --git a/frontend/style/accordion.css b/frontend/style/accordion.css new file mode 100644 index 0000000..21691d9 --- /dev/null +++ b/frontend/style/accordion.css @@ -0,0 +1,130 @@ +#accordion-container { + width: 95%; + display: flex; + align-items: center; + flex-direction: column; + margin-top: 20px; +} + +.accordion { + width: 100%; + margin-top: 10px; +} + +.clickable { + cursor: pointer; +} + +.open > .accordion-title { + background-color: #00305d; + color: #fff; + border-radius: 4px 4px 0px 0px; + /* diable text selection */ + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.accordion-title { + width: 100%; + display: flex; + flex-direction: row; + background-color: #00305d; + color: #fff; + border-radius: 4px 4px 4px 4px; + /* diable text selection */ + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.title-number { + padding: 10px; + width: 20%; +} + +.title-name { + padding: 10px; + width: 20%; +} + +.title-link { + color: hotpink; +} + +.title-description { + padding: 10px; + width: 35%; +} + +.title-balance { + padding: 10px; + width: 20%; + text-align: right; +} + +.title-indicator { + padding: 10px; + width: 5%; + padding-right: 20px; + text-align: right; +} + +.positive-number { + color: rgb(64, 207, 64); +} + +.negative-number { + color: rgb(255, 51, 10); +} + +.neutral-number { + color: #fff; +} + +.accordion-content { + background-color: #b6b6b6; + color: #000; + padding: 5px 10px 10px 10px; + border-radius: 0px 0px 4px 4px; + cursor: auto; +} + +@media all and (max-width: 1000px) { + .accordion-title { + text-align: center; + background-color: #00305d; + color: #fff; + display: block; + } + + .title-number { + font-weight: 600; + } + + .title-number, + .title-name, + .title-link, + .title-description, + .title-balance { + padding: 0px; + padding-top: 5px; + padding-bottom: 5px; + width: 100%; + text-align: center; + } + + .title-indicator { + padding: 0px; + padding-top: 5px; + padding-bottom: 5px; + width: 100%; + text-align: center; + } +} diff --git a/frontend/style/account.css b/frontend/style/account.css new file mode 100644 index 0000000..47e7e46 --- /dev/null +++ b/frontend/style/account.css @@ -0,0 +1,32 @@ +#creation-container { + width: 80%; + margin-top: 30px; + flex-direction: column; +} + +.save-buttons { + width: 75%; + margin-top: 30px; +} + +.save-button { + padding: 10px; + width: 40%; + margin-left: 20px; + margin-right: 20px; + border-radius: 5px; +} + +.form-data { + width: 100%; + flex-direction: row; + margin-top: 10px; +} + +.form-data-label { + width: 30%; +} + +.form-data-element { + width: 60%; +} \ No newline at end of file diff --git a/frontend/style/detail.css b/frontend/style/detail.css new file mode 100644 index 0000000..112a5b9 --- /dev/null +++ b/frontend/style/detail.css @@ -0,0 +1,186 @@ +#detail-container { + margin-top: 30px; + width: 80%; + display: flex; + flex-direction: column; + align-items: center; +} + +.detail-title { + font-size: larger; + font-weight: 600; +} + +.detail-description { +} + +.description-table { + margin-top: 20px; +} + +.description-table, +.description-table-th, +.description-table-td { + border: 1px solid black; + border-collapse: collapse; + text-align: left; +} + +.description-table-th { + width: 35%; + padding: 5px 20px; +} + +.description-table-td { + width: 65%; + padding: 5px 20px; +} + +.description-table-td:has(> .edit-link) { + width: 65%; + padding: 10px 20px; +} + +.edit-link { + background-color: rgb(226, 226, 226); + border: 1px solid rgb(176, 176, 176); + border-radius: 2.5px; + padding: 5px; + height: 20px; + text-decoration: none; + color: #000; +} + +.edit-link:hover, +.edit-link:active { + background-color: rgb(204, 204, 204); + border: 1px solid rgb(158, 158, 158); + color: #000; + text-decoration: none; +} + +#table-container { + width: 90%; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + margin-bottom: 20px; + margin-top: 25px; +} + +.entry-table { + width: 100%; + margin-top: 20px; +} + +.entry-table, +.entry-table-th, +.entry-table-td { + border: 1px solid black; + border-collapse: collapse; + text-align: left; +} + +.entry-table-th { + padding: 5px 20px; +} + +.entry-table-td { + padding: 5px 20px; +} + +.filter { + width: 50%; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; +} + +.filter-text { + margin-left: 10px; + margin-right: 10px; +} + +.filter-div { + margin-top: 7.5px; + text-align: center; +} + +.search-text { + width: 80%; +} + +.amount-text { + width: 45%; + margin-left: 10px; + margin-right: 10px; +} + +.table { + width: 100%; +} + +.pagination { + width: 100%; + margin-top: 10px; + text-align: right; + display: flex; + justify-content: right; + align-items: center; +} + +.pagination-part { + height: 50px; + width: 33.33%; +} + +.page-button { + margin-left: 10px; + margin-right: 10px; +} + +.dropdown { + border: 1px solid black; + margin-left: 10px; +} + +.clickable { + cursor: pointer; + /* diable text selection */ + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +th > .action-button { + font-size: larger; + background-color: rgb(0, 84, 6); + color: #fff; + border: 1px solid #fff; + border-radius: 5px; + height: 35px; + width: 50px; +} + +td > .action-button { + background-color: rgb(151, 0, 0); + color: #fff; + border: 1px solid #fff; + border-radius: 5px; + height: 35px; + width: 50px; +} + +td > .action-button-edit { + background-color: rgb(228, 228, 228); + color: #000; + border: 1px solid #8c8c8c; + border-radius: 5px; + height: 35px; + width: 50px; +} \ No newline at end of file diff --git a/frontend/style/index.css b/frontend/style/index.css new file mode 100644 index 0000000..0e84dde --- /dev/null +++ b/frontend/style/index.css @@ -0,0 +1,130 @@ +body { + font-family: Verdana, Geneva, Tahoma, sans-serif; + margin: 0px; +} + +#root { + min-height: 100vh; + width: 100%; + display: flex; + align-items: center; + flex-direction: column; +} + +.content-wrap { + width: 100%; + display: flex; + align-items: center; + flex-direction: column; + margin-bottom: 25px; +} + +.link-container { + margin-top: 20px; + cursor: pointer; +} + +.link-container > a { + background-color: #c2c2c2; + padding: 7.5px 10px 7.5px 10px; + border: 0.5px solid black; + border-radius: 5px; + color: #000; + text-decoration: none; +} + +.link-container > a:hover, +.link-container > a:active { + background-color: #969696; + color: #000; + text-decoration: none; +} + +.header { + width: 100%; + padding: 15px 0px 15px 0px; + margin-bottom: 10px; + border-bottom: 2px solid rgb(254, 93, 0); + background-color: #00305d; + color: #fff; +} + +.title { + font-size: x-large; + font-weight: 500; + text-align: left; + cursor: pointer; + margin-left: 25px; +} + +.nav { + margin-left: 5%; + cursor: pointer; + /* diable text selection */ + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.nav:hover, +nav:active { + color: rgb(170, 170, 170); +} + +.footer { + width: 100%; + font-size: x-small; + text-align: center; + padding-top: 10px; + padding-bottom: 10px; + margin-top: auto; + border-top: 2px solid rgb(254, 93, 0); + background-color: #00305d; + color: #fff; +} + +.center { + width: 100%; + display: flex; + justify-content: center; + align-items: center; +} + +input { + padding: 5px 12.5px; + border: 1px solid rgb(182, 182, 182); + border-radius: 2.5px; + color: #000; + font-size: 14px; + height: 25px; + -ms-box-sizing: content-box; + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; + box-sizing: content-box; +} + +select { + padding: 5px 12.5px; + border: 1px solid rgb(182, 182, 182); + border-radius: 2.5px; + color: #000; + font-size: 14px; + height: 25px; + -ms-box-sizing: content-box; + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; + box-sizing: content-box; +} + +input[type="button"] { + cursor: pointer; + padding: 5px; +} + +input[type="button"]:hover, input[type="button"]:active { + background-color: rgb(164, 164, 164); + border-color: rgb(81, 81, 81); +} diff --git a/frontend/style/transaction.css b/frontend/style/transaction.css new file mode 100644 index 0000000..b5edaaa --- /dev/null +++ b/frontend/style/transaction.css @@ -0,0 +1,83 @@ +#creation-container { + width: 80%; + margin-top: 30px; + flex-direction: column; +} + +.save-buttons { + width: 75%; + margin-top: 30px; +} + +.save-button { + padding: 10px; + width: 40%; + margin-left: 20px; + margin-right: 20px; + border-radius: 5px; +} + +.form-data { + width: 100%; + flex-direction: row; + margin-top: 10px; +} + +.form-data-label { + width: 30%; +} + +.form-data-element { + width: 70%; +} + +.transaction-table { + width: 100%; + margin-top: 20px; +} + +.transaction-table, +.transaction-th, +.transaction-td { + border: 1px solid rgb(105, 105, 105); + border-collapse: collapse; + text-align: center; +} + +.transaction-th { + padding: 5px 20px; +} + +.transaction-td { + padding: 10px; +} + +.transaction-input { + width: 100%; + display: block; + width: 100%; + height: 35px; + -ms-box-sizing: border-box; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +th > .action-button { + font-size: larger; + background-color: rgb(0, 84, 6); + color: #fff; + border: 1px solid #fff; + border-radius: 5px; + height: 35px; + width: 50px; +} + +td > .action-button { + background-color: rgb(151, 0, 0); + color: #fff; + border: 1px solid #fff; + border-radius: 5px; + height: 35px; + width: 50px; +} diff --git a/index.js b/index.js index 8b567e0..ac9ea9b 100644 --- a/index.js +++ b/index.js @@ -2,6 +2,7 @@ const express = require("express"); const OpenApiValidator = require("express-openapi-validator"); const app = express(); const port = 3000; +const cors = require("cors") //// Swagger const swaggerUi = require("swagger-ui-express"); @@ -11,8 +12,13 @@ const fs = require("fs"); const file = fs.readFileSync("./open_api.yaml", "utf8"); const swaggerDocument = YAML.parse(file); +// Serve the swagger ui app.use("/docs", swaggerUi.serve, swaggerUi.setup(swaggerDocument)); +// use cors middleware +app.use(cors()) + + // Validator app.use(express.json()) app.use( @@ -35,6 +41,8 @@ app.use((err, req, res, next) => { app.use(express.json()); app.use(express.urlencoded({ extended: true })) +app.use( express.static( __dirname + '/frontend')) + const accountsRoute = require("./routes/accounts"); const transactionsRoute = require("./routes/transactions"); @@ -48,7 +56,7 @@ app.use(`${basepath}/transactions`, transactionsRoute); app.get("/", (req, res) => { - res.send("Hello World!"); + res.sendFile( path.join( __dirname, 'client', 'index.html')) }); app.listen(port, () => { diff --git a/package-lock.json b/package-lock.json index 74aca8f..91ffaa6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,8 +13,12 @@ "cors": "^2.8.5", "express": "^4.19.2", "express-openapi-validator": "^5.1.6", + "npm-watch": "^0.13.0", "swagger-ui-express": "^5.0.0", "yaml": "^2.4.1" + }, + "devDependencies": { + "@flydotio/dockerfile": "^0.5.7" } }, "node_modules/@apidevtools/json-schema-ref-parser": { @@ -28,6 +32,25 @@ "js-yaml": "^4.1.0" } }, + "node_modules/@flydotio/dockerfile": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@flydotio/dockerfile/-/dockerfile-0.5.7.tgz", + "integrity": "sha512-BVkXM2K/jLLYBFos1gT/bYv0YPJue8L4j0dkIsskJI4JRn6rPA9bZjT4sJzkzhdubZuwRGG9cgxj4Cfgt4lHlw==", + "dev": true, + "dependencies": { + "chalk": "^5.3.0", + "diff": "^5.1.0", + "ejs": "^3.1.9", + "shell-quote": "^1.8.1", + "yargs": "^17.7.2" + }, + "bin": { + "dockerfile": "index.js" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@jsdevtools/ono": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", @@ -188,6 +211,42 @@ } } }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/append-field": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", @@ -203,6 +262,17 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, + "node_modules/async": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", + "dev": true + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -232,6 +302,17 @@ "prebuild-install": "^7.1.1" } }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/bindings": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", @@ -273,6 +354,26 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/buffer": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", @@ -343,11 +444,83 @@ "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==" }, + "node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, "node_modules/chownr": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, "node_modules/concat-stream": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", @@ -509,11 +682,41 @@ "node": ">=8" } }, + "node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, "node_modules/encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -549,6 +752,15 @@ "node": ">= 0.4" } }, + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -654,6 +866,47 @@ "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dev": true, + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/finalhandler": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", @@ -692,6 +945,19 @@ "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -700,6 +966,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, "node_modules/get-intrinsic": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", @@ -723,6 +998,17 @@ "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -734,6 +1020,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/has-property-descriptors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", @@ -823,6 +1118,11 @@ } ] }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==" + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -841,11 +1141,92 @@ "node": ">= 0.10" } }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" }, + "node_modules/jake": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.1.tgz", + "integrity": "sha512-61btcOHNnLnsOdtLgA5efqQWjnSi/vow5HbI7HMdKKWqvrKR1bLK3BPlJn9gcSaP2ewuamUSMB5XEy76KUIS2w==", + "dev": true, + "dependencies": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jake/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -945,6 +1326,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/minimist": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", @@ -1015,6 +1407,93 @@ "node": ">=10" } }, + "node_modules/nodemon": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.2.tgz", + "integrity": "sha512-/Ib/kloefDy+N0iRTxIUzyGcdW9lzlnca2Jsa5w73bs3npXjg+WInmiX6VY13mIb6SykkthYX/U5t0ukryGqBw==", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/nodemon/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/nodemon/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/nodemon/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-watch": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/npm-watch/-/npm-watch-0.13.0.tgz", + "integrity": "sha512-MYcgocqCzYA44feZhFoYj69FfSaO0EeRE1gcRcmPaXIpNhUMAhNJ1pwic2C4Hn0OPOQmZKSl90CPgmwvOsVhTg==", + "dependencies": { + "nodemon": "^3.0.1", + "through2": "^4.0.2" + }, + "bin": { + "npm-watch": "cli.js" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -1071,6 +1550,17 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/prebuild-install": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.2.tgz", @@ -1113,6 +1603,11 @@ "node": ">= 0.10" } }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==" + }, "node_modules/pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -1193,6 +1688,26 @@ "node": ">= 6" } }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -1302,6 +1817,15 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, + "node_modules/shell-quote": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", + "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/side-channel": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", @@ -1362,6 +1886,17 @@ "simple-concat": "^1.0.0" } }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -1386,6 +1921,32 @@ "safe-buffer": "~5.2.0" } }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", @@ -1394,6 +1955,18 @@ "node": ">=0.10.0" } }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/swagger-ui-dist": { "version": "5.16.0", "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.16.0.tgz", @@ -1439,6 +2012,25 @@ "node": ">=6" } }, + "node_modules/through2": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", + "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", + "dependencies": { + "readable-stream": "3" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -1447,6 +2039,14 @@ "node": ">=0.6" } }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -1475,6 +2075,11 @@ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==" + }, "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", @@ -1517,6 +2122,23 @@ "node": ">= 0.8" } }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -1530,6 +2152,15 @@ "node": ">=0.4" } }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -1545,6 +2176,33 @@ "engines": { "node": ">= 14" } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } } } } diff --git a/package.json b/package.json index 8d46ace..b4ce23a 100644 --- a/package.json +++ b/package.json @@ -3,9 +3,13 @@ "version": "1.0.0", "description": "Our backend", "main": "index.js", + "watch": { + "run": ["{routes}/*.js", "*.js"] + }, "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "run": "node index.js" + "run": "node index.js", + "watch": "npm-watch" }, "author": "", "license": "ISC", @@ -14,7 +18,11 @@ "cors": "^2.8.5", "express": "^4.19.2", "express-openapi-validator": "^5.1.6", + "npm-watch": "^0.13.0", "swagger-ui-express": "^5.0.0", "yaml": "^2.4.1" + }, + "devDependencies": { + "@flydotio/dockerfile": "^0.5.7" } } diff --git a/routes/accounts.js b/routes/accounts.js index c27fc87..1d6634f 100644 --- a/routes/accounts.js +++ b/routes/accounts.js @@ -13,11 +13,14 @@ router.get("/", (req, res) => { res.send(accs); }); -router.post("/", (req, res, next) => { + + +router.post("/", (req, res) => { const name = req.body.name; const des = req.body.description; const type = req.body.type; - const new_acc = db.prepare(`INSERT INTO accounts (name, qualifiedName, description, type, balance, localBalance) VALUES (?, 'test',?, ?, 0, 0)`).run(name, des, type) + const qualifiedName = req.body.parentAccount + ":" + name + const new_acc = db.prepare(`INSERT INTO accounts (name, qualifiedName, description, type, balance, localBalance) VALUES (?, ? ,?, ?, 0, 0)`).run(name, qualifiedName,des, type) res.status(204).send() console.log(new_acc); @@ -25,7 +28,7 @@ router.post("/", (req, res, next) => { router.get("/:account", (req, res) => { const acc = db - .prepare("SELECT * FROM accounts WHERE qualifiedName = ?") + .prepare("SELECT * FROM accounts WHERE name = ?") .get(req.params.account); if (acc == undefined) { @@ -44,10 +47,13 @@ router.get("/:account", (req, res) => { router.put("/:account", (req, res) => { + const oldname = req.params.account; const name = req.body.name; const description = req.body.description; - const type = req.body.type - const acc = db.prepare(`INSERT INTO accounts (name, description, type) VALUES ('${name}', '${description}', '${type}')`).run() + const type = req.body.type; + const qualifiedName = req.body.parentAccount + ":" + name; + + const acc = db.prepare(`UPDATE accounts SET description = ?, name = ?, qualifiedName = ?, type = ? WHERE name=?`).run(description, name, qualifiedName, type, oldname); if (acc == undefined) { res.status(404).send({ @@ -56,16 +62,14 @@ router.put("/:account", (req, res) => { additionalPropl: {} }) } else { - res.status(204).send({ - OK - }) + res.status(204).send() } }); router.delete("/:account", (req, res) => { - const acc = db.prepare("DELETE FROM accounts WHERE qualifiedName = ? RETURNING *").run(req.params.account) + const acc = db.prepare("DELETE FROM accounts WHERE name = ? RETURNING *").run(req.params.account) - res.status(200).send({OK}) + res.status(200).send() }); diff --git a/routes/transactions.js b/routes/transactions.js index 88bd153..2e205aa 100644 --- a/routes/transactions.js +++ b/routes/transactions.js @@ -2,16 +2,16 @@ const express = require('express'); const router = express.Router(); router.get("/", (req, res) => { + res.send([]) }) router.post("/", (req, res) => { - res.send(req.params); }) router.get("/:id", (req, res) => { - res.send(req.params); + res.send({}) }) router.put("/:id", (req, res) => { diff --git a/test/create_account.hurl b/test/create_account.hurl index a701d67..7321cb2 100644 --- a/test/create_account.hurl +++ b/test/create_account.hurl @@ -1,8 +1,2 @@ POST http://localhost:3000/api/v1/accounts/ -{ - "number": 23123, - "name": "Aktiva", - "description": "Girokonto bei der Musterbank eG", - "type": "default", - "parentAccount": "Aktiva:Barvermögen:Bargeld" -} +{"number":2000,"name":"Meintest","description":"Hallo","type":"default","parentAccount":"meins"}