This commit is contained in:
Makussu 2024-04-18 16:53:26 +02:00
commit fdbe6f8eef
11 changed files with 2033 additions and 0 deletions

26
flake.lock generated Normal file
View File

@ -0,0 +1,26 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1713248628,
"narHash": "sha256-NLznXB5AOnniUtZsyy/aPWOk8ussTuePp2acb9U+ISA=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "5672bc9dbf9d88246ddab5ac454e82318d094bb8",
"type": "github"
},
"original": {
"id": "nixpkgs",
"ref": "nixos-unstable",
"type": "indirect"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

45
flake.nix Normal file
View File

@ -0,0 +1,45 @@
{
description = "A Nix-flake-based Node.js development environment";
inputs.nixpkgs.url = "nixpkgs/nixos-unstable";
outputs =
{ self, nixpkgs }:
let
overlays = [
(final: prev: rec {
nodejs = prev.nodejs_latest;
pnpm = prev.nodePackages.pnpm;
yarn = (prev.yarn.override { inherit nodejs; });
})
];
supportedSystems = [
"x86_64-linux"
"aarch64-linux"
"x86_64-darwin"
"aarch64-darwin"
];
forEachSupportedSystem =
f:
nixpkgs.lib.genAttrs supportedSystems (
system: f { pkgs = import nixpkgs { inherit overlays system; }; }
);
in
{
devShells = forEachSupportedSystem (
{ pkgs }:
{
default = pkgs.mkShell {
packages = with pkgs; [
node2nix
nodejs
pnpm
yarn
prettierd
nodePackages.typescript-language-server
];
};
}
);
};
}

32
index.js Normal file
View File

@ -0,0 +1,32 @@
const express = require('express')
const app = express()
const port = 3000
//// Swagger
const swaggerUi = require('swagger-ui-express')
const YAML = require('yaml')
const fs = require('fs')
const file = fs.readFileSync('./open_api.yaml', 'utf8')
const swaggerDocument = YAML.parse(file)
app.use('/docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument))
// app.use(express.json())
// DB
const db = require('better-sqlite3')('mytest.db');
// Routes
const accountsRoute = require('./routes/accounts')
const transactionsRoute = require('./routes/transactions')
app.use('/accounts', accountsRoute);
app.use('/transactions', transactionsRoute);
app.get('/', (req, res) => {
res.send('Hello World!')
})
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})

672
open_api.yaml Normal file
View File

@ -0,0 +1,672 @@
openapi: 3.0.0
info:
version: 1.0.0
title: Buchhaltungs-API
description: |
Buchhaltungs-API für das Web-Anwendungen-Projekt
servers:
- url: ../api/{apiVersion}
variables:
apiVersion:
enum:
- v1
default: v1
description: API-Version
paths:
/accounts:
get:
tags:
- Konten
description: |
Dieser Endpunkt liefert eine Liste aller Konten zurück.
operationId: book.getAccounts
responses:
200:
description: OK
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/AccountTree'
400:
description: Ungültige Anfrage
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
500:
description: Unerwarteter Fehler
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
post:
tags:
- Konten
description: |
Erstellt ein neues Konto.
Die Kontonummer muss in Verbindung mit dem übergeordneten Konto eindeutig sein.
operationId: book.createAccount
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/Account'
responses:
204:
description: OK
400:
description: Ungültige Anfrage
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
422:
description: Nicht ausführbare Anfrage
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
500:
description: Unerwarteter Fehler
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/accounts/{account}:
get:
tags:
- Konten
description: |
Dieser Endpunkt liefert die Details eines Kontos sowie
die Liste der Buchungen des Kontos.
parameters:
- name: account
in: path
description: |
Qualifizierter Name des Kontos
required: true
schema:
$ref: '#/components/schemas/AccountQualifiedName'
style: simple
operationId: book.getAccount
responses:
200:
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/AccountDetails'
400:
description: Ungültige Anfrage
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
404:
description: Konto existiert nicht
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
500:
description: Unerwarteter Fehler
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
put:
tags:
- Konten
description: |
Ändert die Kontodaten.
Die Kontonummer muss in Verbindung mit dem übergeordneten Konto eindeutig sein.
Der Kontotyp kann nur geändert werden, falls keine Buchungen mit dem Konto verknüpft sind.
parameters:
- name: account
in: path
description: |
Qualifizierter Name des Kontos
required: true
schema:
$ref: '#/components/schemas/AccountQualifiedName'
style: simple
operationId: book.updateAccount
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/Account'
responses:
204:
description: OK
400:
description: Ungültige Anfrage
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
404:
description: Konto existiert nicht
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
422:
description: Nicht ausführbare Anfrage
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
500:
description: Unerwarteter Fehler
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
delete:
tags:
- Konten
description: |
Löscht das Konto.
Diese Anfrage kann nur ausgeführt werden, falls keine Buchungen oder Unterkonten mehr mit dem Konto verknüpft sind.
parameters:
- name: account
in: path
description: |
Qualifizierter Name des Kontos
required: true
schema:
$ref: '#/components/schemas/AccountQualifiedName'
style: simple
operationId: book.deleteAccount
responses:
204:
description: OK
400:
description: Ungültige Anfrage
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
404:
description: Konto existiert nicht
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
422:
description: Nicht ausführbare Anfrage
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
500:
description: Unerwarteter Fehler
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/transactions:
get:
tags:
- Buchungen
description: |
Dieser Endpunkt liefert eine Liste aller Buchungen zurück.
operationId: transaction.getTransactions
responses:
200:
description: OK
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/TransactionListItem'
400:
description: Ungültige Anfrage
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
500:
description: Unerwarteter Fehler
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
post:
tags:
- Buchungen
description: |
Erstellt eine neue Buchung.
operationId: transaction.createTransaction
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/Transaction'
responses:
200:
description: OK
content:
application/json:
schema:
type: object
properties:
id:
$ref: '#/components/schemas/Identifier'
required:
- id
additionalProperties: false
400:
description: Ungültige Anfrage
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
422:
description: Ungültige Transaktion
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
500:
description: Unerwarteter Fehler
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/transactions/{id}:
get:
tags:
- Buchungen
description: |
Zeigt die Details einer Buchung mit allen Konten und Beträgen an.
parameters:
- name: id
in: path
description: Buchungs-ID
required: true
schema:
$ref: '#/components/schemas/Identifier'
style: simple
operationId: transaction.getTransaction
responses:
200:
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/Transaction'
400:
description: Ungültige Anfrage
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
404:
description: Transaktion existiert nicht
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
500:
description: Unerwarteter Fehler
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
put:
tags:
- Buchungen
description: |
Aktualisiert die Buchung.
parameters:
- name: id
in: path
description: Buchungs-ID
required: true
schema:
$ref: '#/components/schemas/Identifier'
style: simple
operationId: transaction.updateTransaction
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/Transaction'
responses:
204:
description: OK
400:
description: Ungültige Anfrage
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
404:
description: Transaktion existiert nicht
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
422:
description: Ungültige Transaktion
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
500:
description: Unerwarteter Fehler
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
delete:
tags:
- Buchungen
description: |
Löscht die Buchung aus allen Konten.
parameters:
- name: id
in: path
description: Buchungs-ID
required: true
schema:
$ref: '#/components/schemas/Identifier'
style: simple
operationId: transaction.deleteTransaction
responses:
204:
description: OK
400:
description: Ungültige Anfrage
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
404:
description: Transaktion existiert nicht
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
500:
description: Unerwarteter Fehler
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/reset:
post:
tags:
- Wartung
description: |
Dieser Endpunkt setzt den Datenbestand zurück.
operationId: admin.doReset
responses:
204:
description: OK
400:
description: Ungültige Anfrage
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
500:
description: Unerwarteter Fehler
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
components:
schemas:
Identifier:
type: integer
minimum: 1
maximum: 99999999
description: ID
example: 1
CurrencyAmount:
type: number
multipleOf: 0.01
description: Geldbetrag in Euro
example: 225.17
CurrencyNonZeroAmount:
description: Geldbetrag in Euro, der nicht Null ist
oneOf:
- type: number
multipleOf: 0.01
minimum: 0
exclusiveMinimum: true
description: Positiver Geldbetrag in Euro
example: 25.17
- type: number
multipleOf: 0.01
maximum: 0
exclusiveMaximum: true
description: Negativer Geldbetrag in Euro
example: -93.11
Account:
type: object
properties:
number:
$ref: '#/components/schemas/AccountNumber'
name:
$ref: '#/components/schemas/AccountName'
description:
$ref: '#/components/schemas/AccountDescription'
type:
$ref: '#/components/schemas/AccountType'
parentAccount:
$ref: '#/components/schemas/AccountQualifiedName'
required:
- number
- name
- type
additionalProperties: false
AccountTree:
type: object
properties:
number:
$ref: '#/components/schemas/AccountNumber'
name:
$ref: '#/components/schemas/AccountName'
qualifiedName:
$ref: '#/components/schemas/AccountQualifiedName'
description:
$ref: '#/components/schemas/AccountDescription'
type:
$ref: '#/components/schemas/AccountType'
balance:
$ref: '#/components/schemas/CurrencyAmount'
localBalance:
$ref: '#/components/schemas/CurrencyAmount'
subaccounts:
type: array
items:
$ref: '#/components/schemas/AccountTree'
required:
- number
- name
- qualifiedName
- type
- balance
- localBalance
- subaccounts
additionalProperties: false
AccountDetails:
type: object
properties:
number:
$ref: '#/components/schemas/AccountNumber'
name:
$ref: '#/components/schemas/AccountName'
qualifiedName:
$ref: '#/components/schemas/AccountQualifiedName'
description:
$ref: '#/components/schemas/AccountDescription'
type:
$ref: '#/components/schemas/AccountType'
balance:
$ref: '#/components/schemas/CurrencyAmount'
localBalance:
$ref: '#/components/schemas/CurrencyAmount'
entries:
type: array
items:
$ref: '#/components/schemas/Entry'
required:
- number
- name
- qualifiedName
- type
- balance
- localBalance
- entries
additionalProperties: false
AccountNumber:
type: integer
minimum: 1
maximum: 99999999
description: Kontonummer (wird insbesondere zur Sortierung genutzt)
example: 1100
AccountName:
type: string
minLength: 3
maxLength: 32
pattern: '^[a-zA-Z0-9äöüßÄÖÜẞ_ -]*$'
description: Name des Kontos
example: Aktiva
AccountQualifiedName:
type: string
minLength: 3
description: Name des Kontos mit übergeordneten Konten (qualifizierter Name)
example: Aktiva:Barvermögen:Bargeld
AccountDescription:
type: string
minLength: 3
maxLength: 64
description: Beschreibung des Kontos
example: Girokonto bei der Musterbank eG
AccountType:
type: string
enum:
- default
- meta
description: Art des Kontos (nur Platzhalter für Unterkonten oder eigene Buchungen erlaubt)
example: default
Entry:
type: object
properties:
id:
$ref: '#/components/schemas/Identifier'
postingDate:
$ref: '#/components/schemas/EntryPostingDate'
valueDate:
$ref: '#/components/schemas/EntryValueDate'
title:
$ref: '#/components/schemas/EntryTitle'
amount:
$ref: '#/components/schemas/CurrencyNonZeroAmount'
label:
$ref: '#/components/schemas/EntryLabel'
offsetAccounts:
type: string
description: Gegenkonto (ggf. mehrere bei mehrteiliger Buchung)
required:
- id
- postingDate
- valueDate
- title
- amount
- offsetAccounts
additionalProperties: false
EntryPostingDate:
type: string
format: date
description: Buchungsdatum
EntryValueDate:
type: string
format: date
description: Valuta (Wertstellungsdatum)
EntryTitle:
type: string
minLength: 3
maxLength: 64
description: Buchungstitel
example: Semesterbeitrag SoSe 2024
EntryLabel:
type: string
minLength: 3
maxLength: 64
description: Buchungstitel des Kontos
Transaction:
type: object
properties:
postingDate:
$ref: '#/components/schemas/EntryPostingDate'
valueDate:
$ref: '#/components/schemas/EntryValueDate'
title:
$ref: '#/components/schemas/EntryTitle'
entries:
type: array
minItems: 2
items:
type: object
properties:
account:
$ref: '#/components/schemas/AccountQualifiedName'
amount:
$ref: '#/components/schemas/CurrencyNonZeroAmount'
label:
$ref: '#/components/schemas/EntryLabel'
required:
- account
- amount
additionalProperties: false
required:
- postingDate
- valueDate
- title
- entries
additionalProperties: false
TransactionListItem:
type: object
properties:
id:
$ref: '#/components/schemas/Identifier'
postingDate:
$ref: '#/components/schemas/EntryPostingDate'
valueDate:
$ref: '#/components/schemas/EntryValueDate'
title:
$ref: '#/components/schemas/EntryTitle'
required:
- id
- postingDate
- valueDate
- title
additionalProperties: false
Error:
required:
- message
properties:
code:
type: integer
format: int32
message:
type: string
additionalProperties: true

1153
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

18
package.json Normal file
View File

@ -0,0 +1,18 @@
{
"name": "uni-backend",
"version": "1.0.0",
"description": "Our backend",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"better-sqlite3": "^9.5.0",
"cors": "^2.8.5",
"express": "^4.19.2",
"swagger-ui-express": "^5.0.0",
"yaml": "^2.4.1"
}
}

25
routes/accounts.js Normal file
View File

@ -0,0 +1,25 @@
const express = require('express');
const router = express.Router();
router.get("/", (req, res) => {
})
router.post("/", (req, res) => {
res.send(req.params);
})
router.get("/:account", (req, res) => {
res.send(req.params);
})
router.put("/:account", (req, res) => {
})
router.delete("/:account", (req, res) => {
})
module.exports = router;

0
routes/reset.js Normal file
View File

25
routes/transactions.js Normal file
View File

@ -0,0 +1,25 @@
const express = require('express');
const router = express.Router();
router.get("/", (req, res) => {
})
router.post("/", (req, res) => {
res.send(req.params);
})
router.get("/:id", (req, res) => {
res.send(req.params);
})
router.put("/:id", (req, res) => {
})
router.delete("/:id", (req, res) => {
})
module.exports = router;

11
sql/create_account.sql Normal file
View File

@ -0,0 +1,11 @@
INSERT INTO accounts(
name,
qualifiedName,
description,
type,
balance,
localBalance,
)
VALUES (
'meins', 'Aktiva:meins', 'Mein Geld lol', 'normal', 0.0, 0.0
)

26
sql/create_db.sql Normal file
View File

@ -0,0 +1,26 @@
CREATE TABLE accounts (
account_id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
qualifiedName TEXT NOT NULL,
description TEXT NOT NULL,
type TEXT NOT NULL,
balance FLOAT NOT NULL,
localBalance FLOAT NOT NULL
);
CREATE TABLE transactions (
transaction_id INTEGER PRIMARY KEY,
postingDate TEXT NOT NULL,
valueDate TEXT,
title TEXT NOT NULL
);
CREATE TABLE transaction_entries (
transaction_id INT,
account_id INT,
amount FLOAT,
label TEXT,
FOREIGN KEY (transaction_id) REFERENCES transactions(transaction_id)
FOREIGN KEY (account_id) REFERENCES account(account_id)
PRIMARY KEY (transaction_id, account_id)
);