From d64489aed430cad19ed25cc332f0cca81cf31fb2 Mon Sep 17 00:00:00 2001 From: Theis Gaedigk Date: Mon, 24 Nov 2025 16:05:56 +0100 Subject: [PATCH 1/6] edited scheme but still not working --- backendV2/routes/api/api.database.js | 11 +++++++++++ backendV2/routes/api/api.route.js | 12 ++++++++++++ backendV2/schemeV2.sql | 13 +++++++++---- 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/backendV2/routes/api/api.database.js b/backendV2/routes/api/api.database.js index 507453b..4478d00 100644 --- a/backendV2/routes/api/api.database.js +++ b/backendV2/routes/api/api.database.js @@ -114,3 +114,14 @@ export const getAllLoansV2 = async () => { } return { success: false }; }; + +export const openDoor = async (doorKey) => { + const [result] = await pool.query( + "SELECT lockers FROM doorKeys WHERE door_key = ?;", + [doorKey] + ); + if (result.length > 0) { + return { success: true, data: result[0] }; + } + return { success: false }; +}; diff --git a/backendV2/routes/api/api.route.js b/backendV2/routes/api/api.route.js index 4d1dcee..8edec6e 100644 --- a/backendV2/routes/api/api.route.js +++ b/backendV2/routes/api/api.route.js @@ -10,6 +10,7 @@ import { setTakeDateV2, setReturnDateV2, getLoanByCodeV2, + openDoor, } from "./api.database.js"; // Route for API to get all items from the database @@ -79,4 +80,15 @@ router.post( } ); +router.get("/open-door/:key/:doorKey", authenticate, async (req, res) => { + const doorKey = req.params.doorKey; + + const result = await openDoor(doorKey); + if (result.success) { + res.status(200).json({ data: result.data }); + } else { + res.status(500).json({ message: "Failed to open door" }); + } +}); + export default router; diff --git a/backendV2/schemeV2.sql b/backendV2/schemeV2.sql index 934ed5a..05c9b24 100644 --- a/backendV2/schemeV2.sql +++ b/backendV2/schemeV2.sql @@ -37,14 +37,12 @@ CREATE TABLE items ( item_name varchar(255) NOT NULL UNIQUE, can_borrow_role INT NOT NULL, in_safe bool NOT NULL DEFAULT true, - safe_nr CHAR(2) DEFAULT NULL, + safe_nr INT DEFAULT NULL UNIQUE, entry_created_at timestamp NULL DEFAULT CURRENT_TIMESTAMP, entry_updated_at timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, last_borrowed_person varchar(255) DEFAULT NULL, currently_borrowing varchar(255) DEFAULT NULL, - PRIMARY KEY (id), - CHECK (safe_nr REGEXP '^[0-9]{2}$' OR safe_nr IS NULL), - UNIQUE KEY ux_items_safe_nr (safe_nr) + PRIMARY KEY (id) ) ENGINE=InnoDB; CREATE TABLE apiKeys ( @@ -55,4 +53,11 @@ CREATE TABLE apiKeys ( entry_created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), CHECK (api_key REGEXP '^[0-9]{8}$') +) ENGINE=InnoDB; + +CREATE TABLE doorKeys ( + id int NOT NULL AUTO_INCREMENT, + door_key INT NOT NULL, + lockers INT NOT NULL, + PRIMARY KEY (id) ) ENGINE=InnoDB; \ No newline at end of file From df6b5eac597f1fb204c7b2cbe82e3275754209fe Mon Sep 17 00:00:00 2001 From: Theis Gaedigk Date: Mon, 24 Nov 2025 16:55:27 +0100 Subject: [PATCH 2/6] fixed bug and edited version --- backendV2/info.json | 4 ++-- backendV2/routes/app/database/loansMgmt.database.js | 12 ++++++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/backendV2/info.json b/backendV2/info.json index 331fa29..aa09c6e 100644 --- a/backendV2/info.json +++ b/backendV2/info.json @@ -1,11 +1,11 @@ { "backend-info": { - "version": "v2.0 (dev)" + "version": "v2.0.1 (dev)" }, "frontend-info": { "version": "v2.0 (dev)" }, "admin-panel-info": { - "version": "v1.2 (dev)" + "version": "v1.3 (dev)" } } \ No newline at end of file diff --git a/backendV2/routes/app/database/loansMgmt.database.js b/backendV2/routes/app/database/loansMgmt.database.js index 4bea657..99cdbaa 100644 --- a/backendV2/routes/app/database/loansMgmt.database.js +++ b/backendV2/routes/app/database/loansMgmt.database.js @@ -69,12 +69,20 @@ export const createLoanInDatabase = async ( ) .filter(Boolean); - // Build lockers array (unique, only 2-digit strings) + // Build lockers array (unique, only 2-digit numbers from safe_nr) const lockers = [ ...new Set( itemsRows .map((r) => r.safe_nr) - .filter((sn) => typeof sn === "string" && /^\d{2}$/.test(sn)) + .filter( + (sn) => + sn !== null && + sn !== undefined && + Number.isInteger(Number(sn)) && + Number(sn) >= 0 && + Number(sn) <= 99 + ) + .map((sn) => Number(sn)) ), ]; From fd2ccaa74733020e0f74ebac489a82f39e03caca Mon Sep 17 00:00:00 2001 From: Theis Gaedigk Date: Mon, 24 Nov 2025 17:12:37 +0100 Subject: [PATCH 3/6] feat: add door_key field to items and update related logic in forms and database --- admin/src/components/AddItemForm.tsx | 5 ++--- admin/src/components/ItemTable.tsx | 21 +++++++++++++++++++ admin/src/utils/userActions.ts | 5 +++-- .../admin/database/itemDataMgmt.database.js | 7 ++++--- backendV2/routes/admin/itemDataMgmt.route.js | 5 +++-- backendV2/routes/api/api.database.js | 12 +++++++++-- backendV2/routes/api/api.route.js | 1 + backendV2/schemeV2.sql | 8 +------ 8 files changed, 45 insertions(+), 19 deletions(-) diff --git a/admin/src/components/AddItemForm.tsx b/admin/src/components/AddItemForm.tsx index e48247a..e88bbd0 100644 --- a/admin/src/components/AddItemForm.tsx +++ b/admin/src/components/AddItemForm.tsx @@ -29,8 +29,8 @@ const AddItemForm: React.FC = ({ onClose, alert }) => { - Schließfachnummer (immer zwei Zahlen) - + Schließfachnummer + Ausleih-Berechtigung (Rolle) @@ -64,7 +64,6 @@ const AddItemForm: React.FC = ({ onClose, alert }) => { const safeNr = safeNrValue === "" ? null : safeNrValue; if (!name || Number.isNaN(role)) return; - if (safeNr !== null && !/^\d{2}$/.test(safeNr)) return; const res = await createItem(name, role, safeNr); if (res.success) { diff --git a/admin/src/components/ItemTable.tsx b/admin/src/components/ItemTable.tsx index 16336e0..ccadef0 100644 --- a/admin/src/components/ItemTable.tsx +++ b/admin/src/components/ItemTable.tsx @@ -38,6 +38,7 @@ type Items = { can_borrow_role: string; in_safe: boolean; safe_nr: string; + door_key: string; entry_created_at: string; entry_updated_at: string; last_borrowed_person: string | null; @@ -72,6 +73,12 @@ const ItemTable: React.FC = () => { ); }; + const handleDoorKeyChange = (id: number, value: string) => { + setItems((prev) => + prev.map((it) => (it.id === id ? { ...it, door_key: value } : it)) + ); + }; + const setError = ( status: "error" | "success", message: string, @@ -204,6 +211,9 @@ const ItemTable: React.FC = () => { Schließfachnummer + + Schlüssel + Eintrag erstellt am @@ -290,6 +300,16 @@ const ItemTable: React.FC = () => { value={item.safe_nr} /> + + + handleDoorKeyChange(item.id, e.target.value) + } + value={item.door_key} + /> + {formatDateTime(item.entry_created_at)} {formatDateTime(item.entry_updated_at)} {item.last_borrowed_person} @@ -301,6 +321,7 @@ const ItemTable: React.FC = () => { item.id, item.item_name, item.safe_nr, + item.door_key, item.can_borrow_role ).then((response) => { if (response.success) { diff --git a/admin/src/utils/userActions.ts b/admin/src/utils/userActions.ts index 06974df..85ebc25 100644 --- a/admin/src/utils/userActions.ts +++ b/admin/src/utils/userActions.ts @@ -184,7 +184,7 @@ export const createItem = async ( return { success: false, message: - "Fehler beim Erstellen des Gegenstands. Der Name des Gegenstandes darf nicht mehrmals vergeben werden.", + "Fehler beim Erstellen des Gegenstands. Der Name des Gegenstandes und die Schließfachnummer dürfen nicht mehrmals vergeben werden.", }; } return { success: true }; @@ -198,6 +198,7 @@ export const handleEditItems = async ( itemId: number, item_name: string, safe_nr: string | null, + door_key: string | null, can_borrow_role: string ) => { try { @@ -209,7 +210,7 @@ export const handleEditItems = async ( "Content-Type": "application/json", Authorization: `Bearer ${Cookies.get("token")}`, }, - body: JSON.stringify({ item_name, safe_nr, can_borrow_role }), + body: JSON.stringify({ item_name, safe_nr, door_key, can_borrow_role }), } ); if (!response.ok) { diff --git a/backendV2/routes/admin/database/itemDataMgmt.database.js b/backendV2/routes/admin/database/itemDataMgmt.database.js index 2637f9c..0593bde 100644 --- a/backendV2/routes/admin/database/itemDataMgmt.database.js +++ b/backendV2/routes/admin/database/itemDataMgmt.database.js @@ -36,7 +36,8 @@ export const editItemById = async ( itemId, item_name, can_borrow_role, - safe_nr + safe_nr, + door_key ) => { let newSafeNr; if (safe_nr === null || safe_nr === "") { @@ -45,8 +46,8 @@ export const editItemById = async ( newSafeNr = safe_nr; } const [result] = await pool.query( - "UPDATE items SET item_name = ?, can_borrow_role = ?, safe_nr = ?, entry_updated_at = NOW() WHERE id = ?", - [item_name, can_borrow_role, newSafeNr, itemId] + "UPDATE items SET item_name = ?, can_borrow_role = ?, safe_nr = ?, door_key = ?, entry_updated_at = NOW() WHERE id = ?", + [item_name, can_borrow_role, newSafeNr, door_key, itemId] ); if (result.affectedRows > 0) return { success: true }; return { success: false }; diff --git a/backendV2/routes/admin/itemDataMgmt.route.js b/backendV2/routes/admin/itemDataMgmt.route.js index 77eb6ad..95c1ee5 100644 --- a/backendV2/routes/admin/itemDataMgmt.route.js +++ b/backendV2/routes/admin/itemDataMgmt.route.js @@ -41,13 +41,14 @@ router.post("/create-item", authenticateAdmin, async (req, res) => { router.post("/edit-item/:id", authenticateAdmin, async (req, res) => { const itemId = req.params.id; - const { item_name, can_borrow_role, safe_nr } = req.body; + const { item_name, can_borrow_role, safe_nr, door_key } = req.body; const result = await editItemById( itemId, item_name, can_borrow_role, - safe_nr + safe_nr, + door_key ); if (result.success) { return res.status(200).json({ message: "Item edited successfully" }); diff --git a/backendV2/routes/api/api.database.js b/backendV2/routes/api/api.database.js index 4478d00..d80365c 100644 --- a/backendV2/routes/api/api.database.js +++ b/backendV2/routes/api/api.database.js @@ -117,11 +117,19 @@ export const getAllLoansV2 = async () => { export const openDoor = async (doorKey) => { const [result] = await pool.query( - "SELECT lockers FROM doorKeys WHERE door_key = ?;", + "SELECT safe_nr, id FROM items WHERE door_key = ?;", [doorKey] ); if (result.length > 0) { - return { success: true, data: result[0] }; + const [changeItemSate] = await pool.query( + "UPDATE items SET in_safe = NOT in_safe WHERE id = ?", + [result[0].id] + ); + if (changeItemSate.affectedRows > 0) { + return { success: true, data: result[0] }; + } else { + return { success: false }; + } } return { success: false }; }; diff --git a/backendV2/routes/api/api.route.js b/backendV2/routes/api/api.route.js index 8edec6e..56a289d 100644 --- a/backendV2/routes/api/api.route.js +++ b/backendV2/routes/api/api.route.js @@ -80,6 +80,7 @@ router.post( } ); +// Route for API to open a door router.get("/open-door/:key/:doorKey", authenticate, async (req, res) => { const doorKey = req.params.doorKey; diff --git a/backendV2/schemeV2.sql b/backendV2/schemeV2.sql index 05c9b24..95ff6ed 100644 --- a/backendV2/schemeV2.sql +++ b/backendV2/schemeV2.sql @@ -38,6 +38,7 @@ CREATE TABLE items ( can_borrow_role INT NOT NULL, in_safe bool NOT NULL DEFAULT true, safe_nr INT DEFAULT NULL UNIQUE, + door_key INT DEFAULT NULL UNIQUE, entry_created_at timestamp NULL DEFAULT CURRENT_TIMESTAMP, entry_updated_at timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, last_borrowed_person varchar(255) DEFAULT NULL, @@ -53,11 +54,4 @@ CREATE TABLE apiKeys ( entry_created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), CHECK (api_key REGEXP '^[0-9]{8}$') -) ENGINE=InnoDB; - -CREATE TABLE doorKeys ( - id int NOT NULL AUTO_INCREMENT, - door_key INT NOT NULL, - lockers INT NOT NULL, - PRIMARY KEY (id) ) ENGINE=InnoDB; \ No newline at end of file From 0577a63205b3e621d1f1a675d07e88139a64305a Mon Sep 17 00:00:00 2001 From: Theis Gaedigk Date: Tue, 25 Nov 2025 16:15:08 +0100 Subject: [PATCH 4/6] deleted xls --- backendV2/scheme.xlsx | Bin 12127 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 backendV2/scheme.xlsx diff --git a/backendV2/scheme.xlsx b/backendV2/scheme.xlsx deleted file mode 100644 index 156790c750795d96b35202fbb046495b944f49bd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12127 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 nB7Qgj>ze;(a~$eFng2@x Date: Tue, 25 Nov 2025 16:57:20 +0100 Subject: [PATCH 5/6] fixed bug: mailer did not send email --- backendV2/routes/app/services/mailer.js | 37 +++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/backendV2/routes/app/services/mailer.js b/backendV2/routes/app/services/mailer.js index 9b03858..337894a 100644 --- a/backendV2/routes/app/services/mailer.js +++ b/backendV2/routes/app/services/mailer.js @@ -2,6 +2,38 @@ import nodemailer from "nodemailer"; import dotenv from "dotenv"; dotenv.config(); +const formatDateTime = (value) => { + if (value == null) return "N/A"; + + const toOut = (d) => { + if (!(d instanceof Date) || isNaN(d.getTime())) return "N/A"; + const dd = String(d.getDate()).padStart(2, "0"); + const mm = String(d.getMonth() + 1).padStart(2, "0"); + const yyyy = d.getFullYear(); + const hh = String(d.getHours()).padStart(2, "0"); + const mi = String(d.getMinutes()).padStart(2, "0"); + return `${dd}.${mm}.${yyyy} ${hh}:${mi} Uhr`; + }; + + if (value instanceof Date) return toOut(value); + if (typeof value === "number") return toOut(new Date(value)); + + const s = String(value).trim(); + + // Direct pattern: "YYYY-MM-DD[ T]HH:mm[:ss]" + const m = s.match(/^(\d{4})-(\d{2})-(\d{2})[ T](\d{2}):(\d{2})(?::\d{2})?/); + if (m) { + const [, y, M, d, h, min] = m; + return `${d}.${M}.${y} ${h}:${min} Uhr`; + } + + // ISO or other parseable formats + const dObj = new Date(s); + if (!isNaN(dObj.getTime())) return toOut(dObj); + + return "N/A"; +}; + function buildLoanEmail({ user, items, startDate, endDate, createdDate }) { const brand = process.env.MAIL_BRAND_COLOR || "#0ea5e9"; const itemsList = @@ -142,7 +174,8 @@ export function sendMailLoan(user, items, startDate, endDate, createdDate) { html: buildLoanEmail({ user, items, startDate, endDate, createdDate }), }); - console.log("Message sent:", info.messageId); + // debugging logs + // console.log("Message sent:", info.messageId); })(); - console.log("sendMailLoan called"); + // console.log("sendMailLoan called"); } From 3bf5560834970750ca61a9dab5dc4b73e16d1b88 Mon Sep 17 00:00:00 2001 From: Theis Gaedigk Date: Tue, 25 Nov 2025 17:09:22 +0100 Subject: [PATCH 6/6] edited docs --- Docs/backend_API_docs/README.md | 414 +++++++++++++++++--------------- 1 file changed, 221 insertions(+), 193 deletions(-) diff --git a/Docs/backend_API_docs/README.md b/Docs/backend_API_docs/README.md index eeaa3ed..a18ebb2 100644 --- a/Docs/backend_API_docs/README.md +++ b/Docs/backend_API_docs/README.md @@ -1,87 +1,88 @@ -# Backend API (V2) Documentation +# Borrow System API Documentation -This document describes the current backend API routes and their real response shapes, based on the code in `backendV2`. - ---- - -## Base URLs - -- Frontend: `https://insta.the1s.de` -- Backend: `https://backend.insta.the1s.de` -- Base path: `https://backend.insta.the1s.de/api` - -Service status: `https://status.the1s.de` +**Frontend:** https://insta.the1s.de +**Backend base URL:** `https://backend.insta.the1s.de/api` --- ## Authentication -All **protected** endpoints require an API key as a path parameter `:key`. +All API endpoints require **either**: -Rules for `:key`: +### 1. Bearer Token (JWT) -- Exactly 8 characters -- Digits only (`^[0-9]{8}$`) +Send an `Authorization` header: + +```http +Authorization: Bearer +``` + +- Used for user-based access. +- Token must be valid and not expired. + +### 2. API Key (for devices / machine-to-machine) + +Include an API key in the route as `:key` parameter: + +```text +/api/.../:key/... +``` Example: ```http -GET /api/items/12345678 +GET /api/items/ABC123 ``` -On missing / invalid key: - -- Status: `401 Unauthorized` -- Body (exact message depends on `authenticate` in `backendV2/services/authentication.js`) - -Auth-related modules: - -- `backendV2/services/authentication.js` -- `backendV2/services/database.js` - -Route handlers: - -- `backendV2/routes/api/api.route.js` -- `backendV2/routes/api/api.database.js` +Where `ABC123` is your API key. +The API key is validated server-side. --- -## Endpoints (Overview) +## Common Response Codes -1. **Public** - - - `GET /api/all-items` – List all items (no auth; from original docs) - -2. **Items (authenticated)** - - - `GET /api/items/:key` – List all items - - `POST /api/change-state/:key/:itemId/:state` – Toggle item safe state - -3. **Loans (authenticated)** - - `GET /api/get-loan-by-code/:key/:loan_code` – Get loan by code - - `POST /api/set-take-date/:key/:loan_code` – Set “take” date and mark items as out - - `POST /api/set-return-date/:key/:loan_code` – Set “return” date and mark items as returned +- `200 OK` – Request was successful. +- `401 Unauthorized` – Missing or malformed credentials. +- `403 Forbidden` – Credentials invalid or not allowed to access this resource. +- `404 Not Found` – Resource (e.g., loan) not found. +- `500 Internal Server Error` – Unexpected server error. --- -## 1) Items +## Endpoints -### 1.1 Get all items +### 1. Get All Items **GET** `/api/items/:key` -Returns all items wrapped in a `data` property. +Returns a list of all items. -- Handler: `getItemsFromDatabaseV2` in `api.database.js` -- SQL: `SELECT * FROM items;` +#### Path Parameters -#### Example request +- `:key` – API key (string) + +#### Authentication + +- Either: + - Valid `Authorization: Bearer ` + - Or valid `:key` path parameter + +#### Request Example ```http -GET https://backend.insta.the1s.de/api/items/12345678 +GET /api/items/ABC123 HTTP/1.1 +Host: backend.insta.the1s.de ``` -#### Successful response +or + +```http +GET /api/items/dummyKey HTTP/1.1 +Host: backend.insta.the1s.de +Authorization: Bearer +``` + +#### Successful Response (200) ```json { @@ -90,8 +91,9 @@ GET https://backend.insta.the1s.de/api/items/12345678 "id": 1, "item_name": "DJI 1er Mikro", "can_borrow_role": 4, - "in_safe": 1, - "safe_nr": "01", + "inSafe": 1, + "safe_nr": 3, + "door_key": "123", "entry_created_at": "2025-08-19T22:02:16.000Z", "entry_updated_at": "2025-08-19T22:02:16.000Z", "last_borrowed_person": "alice", @@ -101,245 +103,271 @@ GET https://backend.insta.the1s.de/api/items/12345678 } ``` -#### Error response +#### Error Response (500) ```json -{ "message": "Failed to fetch items" } +{ + "message": "Failed to fetch items" +} ``` -#### Status codes - -- `200 OK` – success, `data` is an array (possibly empty) -- `401 Unauthorized` – invalid / missing key -- `500 Internal Server Error` – database error or `success: false` from DB layer - --- -### 2.2 Toggle item safe state +### 2. Toggle Item Safe State + +Toggles `in_safe` between `0` and `1` for a given item. + +**Keep in mind that when you return a loan by code, the item states are automatically updated.** **POST** `/api/change-state/:key/:itemId` -> You do not need this endpoint to set the states of the items when the items are taken out or returned. When you take or return a loan, the item states are set automatically by the loan endpoints. This endpoint is only for manually toggling the `inSafe` state of an item. +#### Path Parameters -Path parameters: +- `:key` – API key (string) +- `:itemId` – Item ID (integer) -- `:key` – API key (8 digits) -- `:itemId` – numeric `id` of the item +#### Authentication -Handler in `api.route.js` calls `changeInSafeStateV2(itemId)`, which executes: +- Either Bearer token or `:key` API key. -```sql -UPDATE items SET in_safe = NOT in_safe WHERE id = ? -``` - -#### Example request +#### Request Example ```http -POST https://backend.insta.the1s.de/api/change-state/12345678/42 +POST /api/change-state/ABC123/42 HTTP/1.1 +Host: backend.insta.the1s.de ``` -(Will toggle `in_safe` for item `42`.) - -#### Successful response (current implementation) +#### Successful Response (200) ```json { - "data": null + "data": {} } ``` -#### Error responses +_(Implementation currently only returns `{ success: true }`, so `data` may be empty.)_ -Invalid `state` (anything other than `"0"` or `"1"`): +#### Error Response (500) ```json -{ "message": "Invalid state value" } +{ + "message": "Failed to update item state" +} ``` -Failed update: - -```json -{ "message": "Failed to update item state" } -``` - -#### Status codes - -- `200 OK` – item state toggled -- `400 Bad Request` – invalid `state` parameter -- `401 Unauthorized` – invalid / missing key -- `500 Internal Server Error` – database/update failure or `success: false` from DB layer - --- -## 3) Loans +### 3. Get Loan by Code -### 3.1 Get loan by code +Fetch loan information by `loan_code`. **GET** `/api/get-loan-by-code/:key/:loan_code` -Path parameters: +#### Path Parameters -- `:key` – API key -- `:loan_code` – 6-digit loan code (`^[0-9]{6}$` per DB constraint) +- `:key` – API key (string) +- `:loan_code` – Loan code (string) -Database layer (`getLoanByCodeV2`) currently selects: +#### Authentication -```sql -SELECT first_name, returned_date, take_date, lockers -FROM loans -WHERE loan_code = ?; -``` +- Either Bearer token or `:key` API key. -#### Example request +#### Request Example ```http -GET https://backend.insta.the1s.de/api/get-loan-by-code/12345678/646473 +GET /api/get-loan-by-code/ABC123/12345 HTTP/1.1 +Host: backend.insta.the1s.de ``` -#### Successful response +#### Successful Response (200) ```json { "data": { - "first_name": "Theis", + "username": "john", "returned_date": null, - "take_date": "2025-08-25T13:23:00.000Z", - "lockers": ["01", "03"] + "take_date": "2025-01-01T10:00:00.000Z", + "lockers": "[1, 2, 3]" } } ``` -#### Error response - -```json -{ "message": "Loan not found" } -``` - -#### Status codes - -- `200 OK` – loan found -- `401 Unauthorized` – invalid / missing key -- `404 Not Found` – no matching loan for this `loan_code` - ---- - -### 3.2 Set take date - -**POST** `/api/set-take-date/:key/:loan_code` - -Path parameters: - -- `:key` – API key -- `:loan_code` – loan code - -#### Example request - -```http -POST https://backend.insta.the1s.de/api/set-take-date/12345678/646473 -``` - -#### Successful response +#### Error Response (404) ```json { - "data": null + "message": "Loan not found" } ``` -#### Error response - -```json -{ "message": "Failed to set take date" } -``` - -#### Status codes - -- `200 OK` – take date set and items marked as out -- `401 Unauthorized` – invalid / missing key -- `500 Internal Server Error` – invalid loan, missing items, or DB error / `success: false` - --- -### 3.3 Set return date +### 4. Set Loan Return Date + +Sets `returned_date = NOW()` on a loan and updates related items: + +- `in_safe = 1` +- `currently_borrowing = NULL` +- `last_borrowed_person = username` **POST** `/api/set-return-date/:key/:loan_code` -Path parameters: +#### Path Parameters -- `:key` – API key -- `:loan_code` – loan code +- `:key` – API key (string) +- `:loan_code` – Loan code (string) -#### Example request +#### Authentication + +- Either Bearer token or `:key` API key. + +#### Request Example ```http -POST https://backend.insta.the1s.de/api/set-return-date/12345678/646473 +POST /api/set-return-date/ABC123/12345 HTTP/1.1 +Host: backend.insta.the1s.de ``` -#### Successful response (current implementation) +#### Successful Response (200) ```json { - "data": null + "data": {} } ``` -#### Error response +#### Error Response (500) ```json -{ "message": "Failed to set return date" } +{ + "message": "Failed to set return date" +} ``` -#### Status codes - -- `200 OK` – return date set and items marked as returned -- `401 Unauthorized` – invalid / missing key -- `500 Internal Server Error` – invalid loan, missing items, or DB error / `success: false` - --- -## Common Response Shapes +### 5. Set Loan Take Date -**Success – list (authenticated items):** +Sets `take_date = NOW()` on a loan and updates related items: + +- `in_safe = 0` +- `currently_borrowing = username` + +**POST** `/api/set-take-date/:key/:loan_code` + +#### Path Parameters + +- `:key` – API key (string) +- `:loan_code` – Loan code (string) + +#### Authentication + +- Either Bearer token or `:key` API key. + +#### Request Example + +```http +POST /api/set-take-date/ABC123/LOAN-12345 HTTP/1.1 +Host: backend.insta.the1s.de +``` + +#### Successful Response (200) ```json { - "data": [ - /* array of rows */ - ] + "data": {} } ``` -**Success – single loan:** +#### Error Response (500) + +```json +{ + "message": "Failed to set take date" +} +``` + +--- + +### 6. Open Door by Door Key + +Looks up an item by its `door_key`, toggles `in_safe`, and returns safe information. + +**GET** `/api/open-door/:key/:doorKey` + +#### Path Parameters + +- `:key` – API key (string) +- `:doorKey` – Door key/token (string) used by hardware to identify the locker. + +#### Authentication + +- Either Bearer token or `:key` API key. + +#### Request Example + +```http +GET /api/open-door/ABC123/123 HTTP/1.1 +Host: backend.insta.the1s.de +``` + +#### Successful Response (200) ```json { "data": { - /* selected loan fields */ + "safe_nr": 5, + "id": 42 } } ``` -**Success – mutations (current code):** +#### Error Response (500) ```json -{ "data": null } +{ + "message": "Failed to open door" +} ``` -**Errors:** +--- + +## Authentication Error Messages + +### Missing credentials + +Status: `401` ```json -{ "message": "Failed to fetch items" } -{ "message": "Failed to update item state" } -{ "message": "Invalid state value" } -{ "message": "Loan not found" } -{ "message": "Failed to set return date" } -{ "message": "Failed to set take date" } +{ + "message": "Unauthorized" +} ``` -**HTTP Status Codes:** +### Invalid JWT -- `200 OK` – operation succeeded -- `400 Bad Request` – invalid `state` parameter -- `401 Unauthorized` – invalid/missing API key -- `404 Not Found` – loan not found -- `500 Internal Server Error` – database / server failure or `success: false` from DB layer +Status: `403` + +```json +{ + "message": "Present token invalid" +} +``` + +### Invalid API Key + +Status: `403` + +```json +{ + "message": "API Key invalid" +} +``` + +--- + +## Notes + +- All responses are JSON. +- Time fields like `take_date` and `returned_date` are in the format returned by MySQL (usually ISO-like strings). +- `loaned_items_id` in the database is stored as a JSON array string (e.g. `"[1,2,3]"`) and is parsed internally; clients do not interact with this field directly via current endpoints.