Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
26b363fc34
|
|||
|
4700faf03c
|
|||
|
79f3692ad2
|
|||
|
5485fd135d
|
|||
|
5558d42bdb
|
|||
|
b74df36bda
|
|||
|
918b9a6b74
|
|||
|
6d32ca13ca
|
|||
|
feffff0898
|
|||
|
5089f94a21
|
|||
|
f5f5da51c8
|
24
.gitea/ISSUE_TEMPLATE/bug_report.md
Normal file
24
.gitea/ISSUE_TEMPLATE/bug_report.md
Normal file
@@ -0,0 +1,24 @@
|
||||
---
|
||||
name: "Bug Report"
|
||||
about: Report an error or unexpected behavior in MiauInv
|
||||
title: "[BUG] "
|
||||
labels: ["Kind/Bug"]
|
||||
---
|
||||
|
||||
### Description
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
### Steps to Reproduce
|
||||
1. Go to '...'
|
||||
2. Click on '...'
|
||||
3. Scroll down to '...'
|
||||
4. See error
|
||||
|
||||
### Expected Behavior
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
### Logs and Error Messages
|
||||
If applicable, add server logs or browser console outputs here:
|
||||
```text
|
||||
Insert logs here
|
||||
```
|
||||
18
.gitea/ISSUE_TEMPLATE/feature_request.md
Normal file
18
.gitea/ISSUE_TEMPLATE/feature_request.md
Normal file
@@ -0,0 +1,18 @@
|
||||
---
|
||||
name: "Feature Request"
|
||||
about: Suggest an idea or enhancement for MiauInv
|
||||
title: "[FEATURE] "
|
||||
labels: ["Kind/Feature"]
|
||||
---
|
||||
|
||||
### Problem Statement
|
||||
Is your feature request related to a problem? Please describe. (e.g., I am frustrated when...)
|
||||
|
||||
### Proposed Solution
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
### Alternative Solutions
|
||||
A clear and concise description of any alternative solutions or features you have considered.
|
||||
|
||||
### Additional Context
|
||||
Add any other context, screenshots, or mockup ideas about the feature request here.
|
||||
14
.gitea/pull_request_template.md
Normal file
14
.gitea/pull_request_template.md
Normal file
@@ -0,0 +1,14 @@
|
||||
## Description
|
||||
Please include a summary of the changes and which issue is fixed.
|
||||
|
||||
Closes # (Issue Number)
|
||||
|
||||
## Type of Change
|
||||
- [ ] Bug fix (non-breaking change which fixes an issue)
|
||||
- [ ] New feature (non-breaking change which adds functionality)
|
||||
- [ ] Chore / Refactoring (code cleanup or configuration updates)
|
||||
|
||||
## Testing Environment
|
||||
- [ ] Verified via local development server
|
||||
- [ ] Verified compilation inside the Docker container
|
||||
- [ ] All unit tests passing successfully
|
||||
36
.gitea/workflows/release-docker.yaml
Normal file
36
.gitea/workflows/release-docker.yaml
Normal file
@@ -0,0 +1,36 @@
|
||||
name: Build and Push Docker Image on Release
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: actions/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: actions/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to Gitea Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: git.miaurizius.de
|
||||
username: ${{ gitea.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
push: true
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: |
|
||||
git.miaurizius.de/${{ gitea.repository }}:latest
|
||||
git.miaurizius.de/${{ gitea.repository }}:${{ gitea.event.release.tag_name }}
|
||||
30
.gitea/workflows/test-and-lint.yaml
Normal file
30
.gitea/workflows/test-and-lint.yaml
Normal file
@@ -0,0 +1,30 @@
|
||||
name: Code Quality and Testing
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, 'feature/**', 'bugfix/**', 'chore/**', 'refactor/**' ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
test-and-lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.22'
|
||||
|
||||
- name: Check Go Formatting (gofmt)
|
||||
run: |
|
||||
if [ -n "$(gofmt -l .)" ]; then
|
||||
echo "The following files are not properly formatted. Please run 'gofmt -w .'"
|
||||
gofmt -l .
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Run Unit Tests
|
||||
run: go test -v ./...
|
||||
@@ -17,5 +17,9 @@ RUN CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH \
|
||||
go build -ldflags "-s -w" -o MiauInv .
|
||||
|
||||
FROM scratch
|
||||
|
||||
COPY --from=builder /app/MiauInv /MiauInv
|
||||
|
||||
COPY --from=builder /app/frontend /frontend
|
||||
|
||||
ENTRYPOINT ["/MiauInv"]
|
||||
86
README.md
86
README.md
@@ -6,6 +6,7 @@ MiauInv is a secure, light-weight inventory, stock, and project allocation track
|
||||
|
||||
* [Technical Specifications](#technical-specifications)
|
||||
* [Architecture Overview](#architecture-overview)
|
||||
* [Detailed Documentation](#detailed-documentation)
|
||||
* [Configuration](#configuration)
|
||||
* [Configuration File (config.yaml)](#configuration-file-configyaml)
|
||||
* [Environment Variables](#environment-variables)
|
||||
@@ -16,6 +17,8 @@ MiauInv is a secure, light-weight inventory, stock, and project allocation track
|
||||
* [Prerequisites](#prerequisites)
|
||||
* [Option 1: Native Local Deployment](#option-1-native-local-deployment)
|
||||
* [Option 2: Docker Deployment (Recommended)](#option-2-docker-deployment-recommended)
|
||||
* [Reverse Proxy Integration with Caddy](#reverse-proxy-integration-with-caddy)
|
||||
* [Images](#images)
|
||||
|
||||
## Technical Specifications
|
||||
|
||||
@@ -41,6 +44,15 @@ MiauInv splits responsibility cleanly across modularized architecture packages:
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
## Detailed Documentation
|
||||
|
||||
For deep dives into specific subsystems, database layouts, and security mechanisms, please refer to the dedicated documentation files:
|
||||
|
||||
* **[Database Schema & Integrity](docs/DATABASE.md):** Comprehensive breakdown of the SQLite table structures, fields, and foreign key relations.
|
||||
* **[Authentication Architecture](docs/AUTHENTICATION.md):** Detailed explanation of the dual-token rotation flow, JWT lifecycle, and frontend loop protection.
|
||||
|
||||
## Configuration
|
||||
|
||||
The system uses a combination of a structural JSON configuration file and environment variables for system runtime flags.
|
||||
@@ -129,8 +141,7 @@ Before deployment, you must generate SSL/TLS certificates since MiauInv enforces
|
||||
mkdir -p appdata
|
||||
|
||||
# Generate self-signed certificate and private key
|
||||
openssl req -x509 -newkey rsa:4096 -keyout appdata/key.pem -out certs/cert.pem -sha256 -days 365 -nodes
|
||||
|
||||
openssl req -x509 -newkey rsa:4096 -keyout appdata/key.pem -out appdata/cert.pem -sha256 -days 365 -nodes
|
||||
```
|
||||
|
||||
### Option 1: Native Local Deployment
|
||||
@@ -142,7 +153,6 @@ openssl req -x509 -newkey rsa:4096 -keyout appdata/key.pem -out certs/cert.pem -
|
||||
export JWT_SECRET="your_minimum_thirty_two_char_secret_key_here"
|
||||
go build -o miauinv main.go
|
||||
./miauinv
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
@@ -167,7 +177,6 @@ services:
|
||||
- JWT_SECRET=SECURE_RANDOM_STRING # Must be at least 32 characters long
|
||||
volumes:
|
||||
- ./appdata:/appdata # To edit your configuration files
|
||||
|
||||
```
|
||||
|
||||
#### 2. Execution Commands
|
||||
@@ -183,7 +192,74 @@ docker-compose ps
|
||||
|
||||
# Monitor execution system logs
|
||||
docker-compose logs -f
|
||||
|
||||
```
|
||||
|
||||
Once running successfully via Docker orchestration loops, navigate your web browser context safely to `https://localhost:8080` to interact with your MiauInv control panel workspace.
|
||||
|
||||
## Reverse Proxy Integration with Caddy
|
||||
|
||||
If you deploy MiauInv behind a global Caddy server, Caddy must act as an HTTPS reverse proxy. Since the MiauInv binary enforces native TLS transport, Caddy needs to be configured to establish a secure backend connection and bypass verification for self-signed backend certificates.
|
||||
|
||||
### 1. Docker Compose Network Configuration
|
||||
Ensure your MiauInv container shares an external network with your Caddy container (e.g., a network named `proxy`). The container does not need to expose public ports since Caddy communicates with it internally over port `8080`.
|
||||
|
||||
```yaml
|
||||
services:
|
||||
miauinv:
|
||||
image: git.miaurizius.de/miaurizius/miauinv:latest
|
||||
container_name: MiauInv
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- proxy
|
||||
environment:
|
||||
- JWT_SECRET=SECURE_RANDOM_STRING
|
||||
volumes:
|
||||
- ./appdata:/appdata
|
||||
|
||||
networks:
|
||||
proxy:
|
||||
external: true
|
||||
```
|
||||
|
||||
### 2. Caddyfile Configuration
|
||||
|
||||
Add the following block to your server's `Caddyfile`. The `https://` prefix forces Caddy to use TLS for the backend connection, and `tls_insecure_skip_verify` allows the proxy to accept the internal self-signed certificate generated during the prerequisites step.
|
||||
|
||||
```caddy
|
||||
inv.yourdomain.com {
|
||||
encode zstd gzip
|
||||
|
||||
reverse_proxy https://miauinv:8080 {
|
||||
transport http {
|
||||
tls_insecure_skip_verify
|
||||
}
|
||||
}
|
||||
|
||||
header {
|
||||
X-Content-Type-Options nosniff
|
||||
Referrer-Policy strict-origin-when-cross-origin
|
||||
Strict-Transport-Security "max-age=31536000; includeSubDomains"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Apply Configuration
|
||||
|
||||
Reload your Caddy instance to apply the reverse proxy routing rules:
|
||||
|
||||
```bash
|
||||
docker compose exec -w /etc/caddy caddy caddy reload
|
||||
```
|
||||
|
||||
## Images
|
||||
#### Dashboard
|
||||
<img src="docs/img/dashboard.png">
|
||||
|
||||
#### Inventory
|
||||
<img src="docs/img/inventory.png">
|
||||
|
||||
#### Locations
|
||||
<img src="docs/img/locations.png">
|
||||
|
||||
#### Projects
|
||||
<img src="docs/img/projects.png">
|
||||
@@ -15,8 +15,6 @@ func AuthMiddleware(secret []byte) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// WICHTIG: Wenn der User auf einer öffentlichen Seite ist,
|
||||
// darf die Middleware KEINEN Auth-Zwang ausüben und nicht redirecten!
|
||||
if r.URL.Path == "/login" || r.URL.Path == "/register" || r.URL.Path == "/" {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
@@ -50,14 +48,12 @@ func AuthMiddleware(secret []byte) func(http.Handler) http.Handler {
|
||||
if strings.HasPrefix(r.URL.Path, "/api/") {
|
||||
http.Error(w, "Invalid token", http.StatusUnauthorized)
|
||||
} else {
|
||||
// Falls das Cookie korrupt oder abgelaufen ist, löschen wir es direkt,
|
||||
// damit das Frontend sauber merkt, dass es weg ist.
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: "access_token",
|
||||
Value: "",
|
||||
Path: "/",
|
||||
MaxAge: -1,
|
||||
HttpOnly: false, // Erlaubt JS das Auslesen
|
||||
HttpOnly: false,
|
||||
})
|
||||
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
sudo docker buildx build \
|
||||
--platform linux/amd64,linux/arm64 \
|
||||
-t git.miaurizius.de/miaurizius/miauinv:latest \
|
||||
-t git.miaurizius.de/miaurizius/miauinv:v1.0.0 \
|
||||
-t git.miaurizius.de/miaurizius/miauinv:v1.0.2 \
|
||||
--push .
|
||||
51
docs/AUTHENTICATION.md
Normal file
51
docs/AUTHENTICATION.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# Authentication Architecture
|
||||
|
||||
MiauInv implements a stateless JSON Web Token (JWT) architecture combined with a persistent database-backed Refresh Token mechanism to provide high security alongside seamless session retention.
|
||||
|
||||
## Token Lifetime and Properties
|
||||
|
||||
| Token Type | Transport Vector | Storage Location | Lifetime | Purpose |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| **Access Token** | HTTP-Only Cookie & Auth Header | Memory / Browser Cookies | 15 Minutes | Signed payload validating current session identity for immediate API interaction. |
|
||||
| **Refresh Token** | Secure Cookie & JSON Payload | LocalStorage / Secure Cookies | 7 Days | Long-lived high-entropy string used to request a new token pair when the Access Token expires. |
|
||||
|
||||
---
|
||||
|
||||
## Token Rotation and Flow
|
||||
|
||||
The application coordinates token validation through cooperative interactions between Go authentication middlewares and the frontend runtime environment.
|
||||
|
||||
### 1. Normal Authenticated Requests
|
||||
During standard interaction loops, the Go server intercepts requests via auth middleware. It checks the incoming context for validity in the following order:
|
||||
1. `Authorization: Bearer <token>` request header.
|
||||
2. `access_token` cookie values.
|
||||
|
||||
If a valid, unexpired Access Token is recovered, the middleware parses the claims (ID, username, role) and injects them into the request context before execution routes fire.
|
||||
|
||||
### 2. Token Refresh Flow
|
||||
When an Access Token expires mid-session, the following workflow occurs automatically:
|
||||
1. The backend rejects an API call or routing intent with an HTTP state indicating token expiration.
|
||||
2. The frontend execution scope identifies the expiration status and reads the `refresh_token` from storage assets.
|
||||
3. The client submits a POST request containing the token payload to `/api/refresh`.
|
||||
4. The backend verifies the signature, looks up the hash inside the `refresh_tokens` table, and verifies that `revoked == 0` and `expires_at > now`.
|
||||
5. If the validation succeeds, a brand-new Access Token and a rotated Refresh Token pair are generated, saved to secure cookies/storage, and the user session continues without explicit re-authentication.
|
||||
|
||||
---
|
||||
|
||||
## Security Mitigations
|
||||
|
||||
### Loop Protection
|
||||
To prevent broken, expired, or malformed credentials from triggering infinite network refresh loops (which degrade browser performance and strain backend lookup performance), the frontend utilizes an explicit safety lock.
|
||||
|
||||
|
||||
```
|
||||
|
||||
Token Expired -> Check 'is_refreshing' flag -> True -> Clear Auth & Force Login
|
||||
-> False -> Set flag 'true' -> Send Request
|
||||
|
||||
```
|
||||
|
||||
Before issuing an evaluation request to `/api/refresh`, the application checks a temporary session variable (`is_refreshing` within `sessionStorage`). If the flag is already set to `true`, the loop protection triggers a hard clearance routine via `clearAllAuth()`, drops all token storage records, and routes the user back to the primary login view safely.
|
||||
|
||||
### Database Revocation
|
||||
Refresh sessions can be killed immediately from the server side. When a user requests `/api/logout`, the backend switches the corresponding row state within the `refresh_tokens` database container to `revoked = 1`. Any subsequent rotation requests relying on that token family are automatically dropped, protecting against stolen credential replay attacks.
|
||||
97
docs/DATABASE.md
Normal file
97
docs/DATABASE.md
Normal file
@@ -0,0 +1,97 @@
|
||||
# Database Documentation
|
||||
|
||||
MiauInv utilizes an embedded SQLite database instance for persistent data storage. Foreign key constraints are strictly enforced at the database level.
|
||||
|
||||
## Configuration
|
||||
To ensure data integrity, every database connection initialization explicitly executes the following command before handling queries:
|
||||
```sql
|
||||
PRAGMA foreign_keys = ON;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Schema Architecture
|
||||
|
||||
### Entity-Relationship Summary
|
||||
|
||||
The database consists of primary entity tables (`users`, `items`, `locations`, `projects`) and relational junction tables (`stock`, `project_items`, `refresh_tokens`) designed to track stock distribution and access sessions.
|
||||
|
||||
```
|
||||
[users] <--- (1:N) ---> [refresh_tokens]
|
||||
[items] <--- (1:N) ---> [stock] <--- (N:1) ---> [locations]
|
||||
[items] <--- (1:N) ---> [project_items] <--- (N:1) ---> [projects]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Table Definitions
|
||||
|
||||
### 1. users
|
||||
|
||||
Stores user credentials and operational roles within the system.
|
||||
|
||||
* **id (TEXT, PK):** Unique UUID
|
||||
* **username (TEXT, Unique):** Unique account identifier.
|
||||
* **password (TEXT):** Hashed user password.
|
||||
* **role (TEXT):** Access control flag (e.g., admin, user).
|
||||
|
||||
### 2. refresh_tokens
|
||||
|
||||
Tracks valid extended sessions linked to specific user accounts.
|
||||
|
||||
* **id (TEXT, PK):** Unique identifier.
|
||||
* **user_id (TEXT, FK):** References `users(id)`.
|
||||
* **token_hash (TEXT):** Cryptographic hash of the active refresh token.
|
||||
* **expires_at (INTEGER):** Unix timestamp indicating token expiration.
|
||||
* **created_at (INTEGER):** Unix timestamp indicating session creation.
|
||||
* **revoked (INTEGER):** Boolean flag (0 or 1) indicating if the session was manually invalidated.
|
||||
* **device_info (TEXT, Optional):** Client metadata for auditing.
|
||||
|
||||
### 3. items
|
||||
|
||||
Represents individual tracked assets.
|
||||
|
||||
* **id (INTEGER, PK, Autoincrement):** Primary key.
|
||||
* **name (TEXT):** Asset designation.
|
||||
* **category (TEXT, Optional):** Grouping classification.
|
||||
* **description (TEXT, Optional):** Detailed asset context.
|
||||
* **total_quantity (INTEGER):** Absolute global stock baseline counter.
|
||||
|
||||
### 4. locations
|
||||
|
||||
Defines logical or physical facilities.
|
||||
|
||||
* **id (INTEGER, PK, Autoincrement):** Primary key.
|
||||
* **name (TEXT, Unique):** Unique facility naming constraint.
|
||||
|
||||
### 5. projects
|
||||
|
||||
Defines distinct tasks or allocation targets.
|
||||
|
||||
* **id (INTEGER, PK, Autoincrement):** Primary key.
|
||||
* **name (TEXT, Unique):** Unique operational tracking name.
|
||||
* **description (TEXT, Optional):** Scope description.
|
||||
|
||||
### 6. stock
|
||||
|
||||
Junction table mapping physical asset distributions across facilities.
|
||||
|
||||
* **id (INTEGER, PK, Autoincrement):** Primary key.
|
||||
* **item_id (INTEGER, FK):** References `items(id)`.
|
||||
* **location_id (INTEGER, FK):** References `locations(id)`.
|
||||
* **quantity (INTEGER):** Specific quantity present at this location node.
|
||||
|
||||
### 7. project_items
|
||||
|
||||
Junction table tracking asset assignments dedicated to specific ongoing project environments.
|
||||
|
||||
* **id (INTEGER, PK, Autoincrement):** Primary key.
|
||||
* **item_id (INTEGER, FK):** References `items(id)`.
|
||||
* **project_id (INTEGER, FK):** References `projects(id)`.
|
||||
* **quantity (INTEGER):** Quantity allocated to this project context.
|
||||
---
|
||||
|
||||
## Data Integrity Constraints
|
||||
|
||||
* **Foreign Keys:** Because standard `ON DELETE` cascades are not defined explicitly in the schema rules, SQLite blocks parent deletion actions if dependent rows exist in `stock` or `project_items`. You must clear out stock allocations and project associations manually before deleting an item, location, or project.
|
||||
* **Uniqueness:** String uniqueness constraints protect against duplicate namespace registration on `users(username)`, `locations(name)`, and `projects(name)`.
|
||||
BIN
docs/img/dashboard.png
Normal file
BIN
docs/img/dashboard.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 57 KiB |
BIN
docs/img/inventory.png
Normal file
BIN
docs/img/inventory.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 38 KiB |
BIN
docs/img/locations.png
Normal file
BIN
docs/img/locations.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 43 KiB |
BIN
docs/img/projects.png
Normal file
BIN
docs/img/projects.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 37 KiB |
@@ -1,6 +1,7 @@
|
||||
package frontend
|
||||
|
||||
import (
|
||||
"MiauInv/storage"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -53,7 +54,28 @@ func Home(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func Dashboard(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
err := dashboard.ExecuteTemplate(w, "base.html", struct {
|
||||
|
||||
var itemHive, projectHive, locationHive int
|
||||
|
||||
err := storage.DB.QueryRow("SELECT COUNT(*) FROM items").Scan(&itemHive)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to count items", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
err = storage.DB.QueryRow("SELECT COUNT(*) FROM projects").Scan(&projectHive)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to count projects", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
err = storage.DB.QueryRow("SELECT COUNT(*) FROM locations").Scan(&locationHive)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to count locations", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
err = dashboard.ExecuteTemplate(w, "base.html", struct {
|
||||
Title string
|
||||
Stats struct {
|
||||
Items int
|
||||
@@ -67,9 +89,9 @@ func Dashboard(w http.ResponseWriter, r *http.Request) {
|
||||
Projects int
|
||||
Locations int
|
||||
}{
|
||||
Items: 1,
|
||||
Projects: 1,
|
||||
Locations: 3,
|
||||
Items: itemHive,
|
||||
Projects: projectHive,
|
||||
Locations: locationHive,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -30,6 +30,12 @@ func APIRegister(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if len(user.Password) > 72 {
|
||||
log.Println("POST [api/register] User password too long")
|
||||
http.Error(w, "Password exceeds the maximum allowed length of 72 characters", http.StatusUnprocessableEntity)
|
||||
return
|
||||
}
|
||||
|
||||
hashed, err := auth.HashPassword(user.Password)
|
||||
if err != nil {
|
||||
log.Println("POST [api/register] " + r.RemoteAddr + ": " + err.Error())
|
||||
|
||||
@@ -573,7 +573,6 @@ func Associations(w http.ResponseWriter, r *http.Request) {
|
||||
idStr := r.URL.Query().Get("id")
|
||||
projectIDStr := r.URL.Query().Get("project_id")
|
||||
|
||||
// Optionaler Filter: Alle Items für ein bestimmtes Projekt holen (?project_id=X)
|
||||
if projectIDStr != "" {
|
||||
pID, _ := strconv.Atoi(projectIDStr)
|
||||
rows, err := storage.DB.Query("SELECT id, item_id, project_id, quantity FROM project_items WHERE project_id = ?", pID)
|
||||
@@ -593,7 +592,6 @@ func Associations(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Einzelne Assoziation anhand der Tabellen-ID (?id=X)
|
||||
if idStr != "" {
|
||||
id, _ := strconv.Atoi(idStr)
|
||||
var pi models.ProjectItem
|
||||
@@ -607,7 +605,6 @@ func Associations(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Gar kein Parameter -> Komplett-Dump aller Zuweisungen
|
||||
rows, err := storage.DB.Query("SELECT id, item_id, project_id, quantity FROM project_items")
|
||||
if err != nil {
|
||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||
|
||||
Reference in New Issue
Block a user