diff --git a/Docs/backend_API_docs/README.md b/Docs/backend_API_docs/README.md index 58ce9bc..9d87fa4 100644 --- a/Docs/backend_API_docs/README.md +++ b/Docs/backend_API_docs/README.md @@ -1,121 +1,283 @@ -# Backend API docs +# Borrow System Backend API -If you want to coorperate with me, or build something new with my backend API, feel free to reach out! +Base URL: `http://localhost:8002` -On this page you will learn how my API works. +- App server: [backend/server.js](backend/server.js) +- Auth/JWT: [`authenticate`](backend/services/tokenService.js), [`generateToken`](backend/services/tokenService.js) +- App API (JWT): [backend/routes/api.js](backend/routes/api.js) +- Admin API (key): [backend/routes/apiV2.js](backend/routes/apiV2.js) +- DB layer: [backend/services/database.js](backend/services/database.js) +- Schema: [backend/scheme.sql](backend/scheme.sql) -## General information +## Authentication -When you look at my backend folder and file structure, yu can see that I have two files called `API`. The first file called `api.js` is for my web frontend. Because this file works together with my JWT token service. +Most endpoints under `/api` require a Bearer JWT. -But I have build a second API. You can see the second API file in the same directory, the file is called `apiV2.js`. +1. Login to get a token -This is the file that you can use to build an API. +- POST /api/login +- Body: `{ "username": string, "password": string }` +- Response 200: `{ "message": "Login successful", "token": string }` +- Response 401: `{ "message": "Invalid credentials" }` -But first you have to get the Admin API key, stored in an .env file on my server. - -### Current endpoints - -- /apiV2/items/`secretKey` - -#### /apiV2/items/ - -When you call this API you will get like this result back: +Example: +```sh +curl -s -X POST http://localhost:8002/api/login \ + -H "Content-Type: application/json" \ + -d '{"username":"alice","password":"password1"}' ``` + +2. Use the token for all protected endpoints + +- Header: `Authorization: Bearer ` + +The middleware [`authenticate`](backend/services/tokenService.js) verifies tokens and attaches `req.user = { username, role }`. + +Environment: + +- SECRET_KEY: HMAC secret for JWT +- DB_HOST, DB_USER, DB_PASSWORD, DB_NAME: MySQL connection +- ADMIN_ID: Admin API key for /apiV2 + +## Data model (items) + +Items (as returned by endpoints) have: + +- `id`: number +- `item_name`: string +- `can_borrow_role`: number (minimum role required) +- `inSafe`: 0/1 (not in locker / in locker) + +See the full schema in [backend/scheme.sql](backend/scheme.sql). + +--- + +## App API (JWT) — /api + +All routes below require `Authorization: Bearer ` unless noted. + +### GET /api/items + +Returns items filtered by the user role: + +- role == 0: all items +- role > 0: items with `can_borrow_role >= role` + +Implements: [`getItemsFromDatabase`](backend/services/database.js) + +Response 200: + +```json +[ + { "id": 1, "item_name": "Laptop", "can_borrow_role": 1, "inSafe": 1 }, + ... +] +``` + +Example: + +```sh +curl -s http://localhost:8002/api/items \ + -H "Authorization: Bearer $TOKEN" +``` + +### GET /api/loans + +Returns all loans. + +Implements: [`getLoansFromDatabase`](backend/services/database.js) + +Response 200: + +```json [ { "id": 1, - "item_name": "DJI 1er Mikro", - "can_borrow_role": "4", - "inSafe": 1 - }, - { - "id": 2, - "item_name": "DJI 2er Mikro 1", - "can_borrow_role": "4", - "inSafe": 1 - }, - { - "id": 3, - "item_name": "DJI 2er Mikro 2", - "can_borrow_role": "4", - "inSafe": 1 - }, - { - "id": 5, - "item_name": "Rode Richt Mikrofon", - "can_borrow_role": "2", - "inSafe": 1 - }, - { - "id": 6, - "item_name": "Kamera Stativ", - "can_borrow_role": "1", - "inSafe": 1 - }, - { - "id": 7, - "item_name": "SONY Kamera - inkl. Akkus und Objektiv", - "can_borrow_role": "1", - "inSafe": 1 - }, - { - "id": 8, - "item_name": "MacBook inkl. Adapter", - "can_borrow_role": "2", - "inSafe": 1 - }, - { - "id": 9, - "item_name": "SD Karten", - "can_borrow_role": "3", - "inSafe": 1 - }, - { - "id": 10, - "item_name": "Kameragimbal", - "can_borrow_role": "1", - "inSafe": 1 - }, - { - "id": 11, - "item_name": "ATEM MINI PRO", - "can_borrow_role": "1", - "inSafe": 1 - }, - { - "id": 12, - "item_name": "Handygimbal", - "can_borrow_role": "4", - "inSafe": 1 - }, - { - "id": 13, - "item_name": "Kameralüfter", - "can_borrow_role": "1", - "inSafe": 1 - }, - { - "id": 14, - "item_name": "Kleine Kamera 1 - inkl. Objektiv", - "can_borrow_role": "2", - "inSafe": 1 - }, - { - "id": 15, - "item_name": "Kleine Kamera 2 - inkl. Objektiv", - "can_borrow_role": "2", - "inSafe": 1 + "username": "alice", + "loan_code": 1001, + "start_date": "2025-08-01T09:00:00.000Z", + "end_date": "2025-08-10T09:00:00.000Z", + "returned_date": null, + "created_at": "2025-08-01T09:00:00.000Z", + "loaned_items_id": [1, 2], + "loaned_items_name": ["Laptop", "Projector"] } ] ``` -There you can see all items and their details. +### GET /api/userLoans -Each item has the following properties: +Returns loans for the authenticated user. -- `id`: The unique identifier for the item. -- `item_name`: The name of the item. -- `can_borrow_role`: The role ID that is allowed to borrow the item. -- `inSafe`: Indicates whether the item is currently in the locker (1) or not (0). +Implements: [`getUserLoansFromDatabase`](backend/services/database.js) + +Response 200: + +- On success: `Loan[]` +- If none found: `"No loans found for this user"` (string) + +Tip: Treat a non-array response as “no loans”. + +### DELETE /api/deleteLoan/:id + +Deletes a loan by numeric ID. + +Implements: [`deleteLoanFromDatabase`](backend/services/database.js) + +- 200: `{ "message": "Loan deleted successfully" }` +- 500: `{ "message": "Failed to delete loan" }` + +Example: + +```sh +curl -s -X DELETE http://localhost:8002/api/deleteLoan/42 \ + -H "Authorization: Bearer $TOKEN" +``` + +### POST /api/borrowableItems + +Returns items available in the given time range (excludes items with overlapping loans). Also enforces role filtering. + +Implements: [`getBorrowableItemsFromDatabase`](backend/services/database.js) + +Request body: + +```json +{ "startDate": "2025-08-01T09:00:00Z", "endDate": "2025-08-02T09:00:00Z" } +``` + +- 200: `Item[]` +- 400: `{ "message": "startDate and endDate are required" }` +- 500: `{ "message": "Failed to fetch borrowable items" }` + +Example: + +```sh +curl -s -X POST http://localhost:8002/api/borrowableItems \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"startDate":"2025-08-01T09:00:00Z","endDate":"2025-08-02T09:00:00Z"}' +``` + +### POST /api/createLoan + +Creates a loan for the authenticated user. + +Implements: [`createLoanInDatabase`](backend/services/database.js) + +Request body: + +```json +{ + "items": [1, 2, 3], // array of item IDs (required) + "startDate": "2025-08-01T09:00:00Z", // required + "endDate": "2025-08-02T09:00:00Z" // required +} +``` + +Notes: + +- IDs are coerced to numbers; invalid entries are dropped. +- Date range must be valid and `startDate < endDate`. +- Overlaps with existing loans cause 409 Conflict. +- On success, returns the generated `loanCode`. + +Responses: + +- 201: + +```json +{ + "message": "Loan created successfully", + "loanId": 123, + "loanCode": 1007 +} +``` + +- 400: `{ "message": "Items array is required" | "No valid item IDs provided" | "Invalid date range" | ... }` +- 409: `{ "message": "Items not available in the selected period" }` +- 500: `{ "message": "Failed to create loan" }` + +Example: + +```sh +curl -s -X POST http://localhost:8002/api/createLoan \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"items":[1,2],"startDate":"2025-08-01T09:00:00Z","endDate":"2025-08-02T09:00:00Z"}' +``` + +--- + +## Admin API — /apiV2 + +These endpoints are protected by a static admin key in the path. Set `ADMIN_ID` in environment. No JWT required. + +### GET /apiV2/items/:key + +Returns all items (no role filtering). + +Implements: [`getItemsFromDatabaseV2`](backend/services/database.js) + +- 200: `Item[]` +- 403: `{ "message": "Access denied" }` + +Example: + +```sh +curl -s http://localhost:8002/apiV2/items/$ADMIN_ID +``` + +### POST /apiV2/controlInSafe/:key/:itemId/:state + +Updates `inSafe` state (0 or 1) for an item by ID. + +Implements: [`changeInSafeStateV2`](backend/services/database.js) + +- `state`: `"0"` or `"1"` +- 200: `{ "message": "Item state updated successfully" }` +- 400: `{ "message": "Invalid state value" }` +- 403: `{ "message": "Access denied" }` +- 500: `{ "message": "Failed to update item state" }` + +Example: + +```sh +curl -s -X POST http://localhost:8002/apiV2/controlInSafe/$ADMIN_ID/5/0 +``` + +--- + +## Error handling summary + +- 400 Bad Request: invalid payloads or missing fields +- 401 Unauthorized: missing/invalid JWT (for `/api` routes) +- 403 Forbidden: wrong admin key (for `/apiV2` routes) +- 409 Conflict: loan overlaps with selected period +- 500 Internal Server Error: unexpected server/database errors + +--- + +## Running locally + +With Docker Compose: [docker-compose.yml](docker-compose.yml) + +- Backend: http://localhost:8002 (mounted from [backend](backend)) +- MySQL: root password from `.env` as `DB_PASSWORD`, port 3309 on host +- Seed schema/data: import [backend/scheme.sql](backend/scheme.sql) into the DB + +Environment required by backend: + +- `DB_HOST`, `DB_USER`, `DB_PASSWORD`, `DB_NAME` +- `SECRET_KEY` +- `ADMIN_ID` + +--- + +References: + +- App routes: [backend/routes/api.js](backend/routes/api.js) + - [`loginFunc`](backend/services/database.js), [`getItemsFromDatabase`](backend/services/database.js), [`getLoansFromDatabase`](backend/services/database.js), [`getUserLoansFromDatabase`](backend/services/database.js), [`deleteLoanFromDatabase`](backend/services/database.js), [`getBorrowableItemsFromDatabase`](backend/services/database.js), [`createLoanInDatabase`](backend/services/database.js) +- Admin routes: [backend/routes/apiV2.js](backend/routes/apiV2.js) +- Auth: [`authenticate`](backend/services/tokenService.js),