feat: Implement Docker setup with MongoDB and website service, add requirements and build script
This commit is contained in:
@@ -0,0 +1,15 @@
|
|||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
*.pyd
|
||||||
|
.Python
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
build/
|
||||||
|
.git/
|
||||||
|
.gitignore
|
||||||
|
mongodb.log
|
||||||
|
mongodb.log.*
|
||||||
|
*.dist-info/
|
||||||
|
.nuitka-cache/
|
||||||
|
*.build/
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
FROM python:3.12-slim AS builder
|
||||||
|
|
||||||
|
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||||
|
PYTHONUNBUFFERED=1
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
ARG NUITKA_JOBS=1
|
||||||
|
ARG NUITKA_LOW_MEMORY=1
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends build-essential patchelf \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
COPY requirements.txt ./
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt \
|
||||||
|
&& pip install --no-cache-dir nuitka ordered-set zstandard
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Build a standalone binary with all required templates/static/data assets.
|
||||||
|
# Defaults are tuned for lower RAM pressure to avoid host crashes.
|
||||||
|
RUN set -eu; \
|
||||||
|
EXTRA_FLAGS=""; \
|
||||||
|
if [ "$NUITKA_LOW_MEMORY" = "1" ]; then \
|
||||||
|
EXTRA_FLAGS="--low-memory --lto=no"; \
|
||||||
|
fi; \
|
||||||
|
python -m nuitka \
|
||||||
|
--standalone \
|
||||||
|
--follow-imports \
|
||||||
|
--assume-yes-for-downloads \
|
||||||
|
--jobs="$NUITKA_JOBS" \
|
||||||
|
--remove-output \
|
||||||
|
--include-data-dir=templates=templates \
|
||||||
|
--include-data-dir=static=static \
|
||||||
|
--include-data-dir=data=data \
|
||||||
|
--output-dir=build \
|
||||||
|
$EXTRA_FLAGS \
|
||||||
|
main.py
|
||||||
|
|
||||||
|
FROM python:3.12-slim AS runtime
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY --from=builder /app/build/main.dist ./
|
||||||
|
|
||||||
|
EXPOSE 4999
|
||||||
|
|
||||||
|
CMD ["./main.bin"]
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
services:
|
||||||
|
mongodb:
|
||||||
|
image: mongo:7
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
MONGO_INITDB_DATABASE: Invario_Website
|
||||||
|
volumes:
|
||||||
|
- mongo_data:/data/db
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "mongosh", "--quiet", "--eval", "db.adminCommand('ping').ok"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 8
|
||||||
|
|
||||||
|
website:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
args:
|
||||||
|
NUITKA_JOBS: ${NUITKA_JOBS:-1}
|
||||||
|
NUITKA_LOW_MEMORY: ${NUITKA_LOW_MEMORY:-1}
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
mongodb:
|
||||||
|
condition: service_healthy
|
||||||
|
environment:
|
||||||
|
MONGO_URI: mongodb://mongodb:27017
|
||||||
|
MONGO_DB_NAME: Invario_Website
|
||||||
|
SESSION_COOKIE_SECURE: "0"
|
||||||
|
JWT_SECRET_KEY: change-this-in-production
|
||||||
|
ports:
|
||||||
|
- "4999:4999"
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
mongo_data:
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
Flask>=3.0,<4.0
|
||||||
|
Flask-JWT-Extended>=4.6,<5.0
|
||||||
|
bleach>=6.1,<7.0
|
||||||
|
pymongo>=4.8,<5.0
|
||||||
Executable
+35
@@ -0,0 +1,35 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
cd "$SCRIPT_DIR"
|
||||||
|
|
||||||
|
if ! command -v docker >/dev/null 2>&1; then
|
||||||
|
echo "Error: docker is not installed."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! docker compose version >/dev/null 2>&1; then
|
||||||
|
echo "Error: docker compose plugin is not available."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
NUITKA_JOBS="${NUITKA_JOBS:-1}"
|
||||||
|
NUITKA_LOW_MEMORY="${NUITKA_LOW_MEMORY:-1}"
|
||||||
|
|
||||||
|
echo "==> Safe Nuitka build settings"
|
||||||
|
echo " NUITKA_JOBS=${NUITKA_JOBS}"
|
||||||
|
echo " NUITKA_LOW_MEMORY=${NUITKA_LOW_MEMORY}"
|
||||||
|
|
||||||
|
echo "==> Building website image (Nuitka standalone conversion runs in Dockerfile)"
|
||||||
|
NUITKA_JOBS="$NUITKA_JOBS" NUITKA_LOW_MEMORY="$NUITKA_LOW_MEMORY" docker compose build website
|
||||||
|
|
||||||
|
echo "==> Starting internal MongoDB + Website containers"
|
||||||
|
docker compose up -d
|
||||||
|
|
||||||
|
echo "==> Container status"
|
||||||
|
docker compose ps
|
||||||
|
|
||||||
|
echo "Application is starting on: http://localhost:4999"
|
||||||
|
echo "Use: docker compose logs -f website"
|
||||||
|
echo "Tip: If build is still too heavy, run with NUITKA_JOBS=1 NUITKA_LOW_MEMORY=1 ./start_docker_build.sh"
|
||||||
+89
-73
@@ -1,8 +1,19 @@
|
|||||||
|
import os
|
||||||
from pymongo import MongoClient
|
from pymongo import MongoClient
|
||||||
import hashlib
|
import hashlib
|
||||||
from bson.objectid import ObjectId
|
from bson.objectid import ObjectId
|
||||||
|
|
||||||
|
|
||||||
|
MONGO_URI = os.environ.get("MONGO_URI", "mongodb://localhost:27017")
|
||||||
|
MONGO_DB_NAME = os.environ.get("MONGO_DB_NAME", "Invario_Website")
|
||||||
|
|
||||||
|
|
||||||
|
def _get_users_collection():
|
||||||
|
client = MongoClient(MONGO_URI, serverSelectionTimeoutMS=1500)
|
||||||
|
db = client[MONGO_DB_NAME]
|
||||||
|
return client, db["users"]
|
||||||
|
|
||||||
|
|
||||||
def check_password_strength(password):
|
def check_password_strength(password):
|
||||||
"""
|
"""
|
||||||
Check if a password meets minimum security requirements.
|
Check if a password meets minimum security requirements.
|
||||||
@@ -42,13 +53,14 @@ def check_nm_pwd(username, password):
|
|||||||
Returns:
|
Returns:
|
||||||
dict: User document if credentials are valid, None otherwise
|
dict: User document if credentials are valid, None otherwise
|
||||||
"""
|
"""
|
||||||
client = MongoClient('localhost', 27017)
|
client = None
|
||||||
db = client['Invario_Website']
|
try:
|
||||||
users = db['users']
|
client, users = _get_users_collection()
|
||||||
hashed_password = hashlib.sha512(password.encode()).hexdigest()
|
hashed_password = hashlib.sha512(password.encode()).hexdigest()
|
||||||
user = users.find_one({'Username': username, 'Password': hashed_password})
|
return users.find_one({'Username': username, 'Password': hashed_password})
|
||||||
client.close()
|
finally:
|
||||||
return user
|
if client:
|
||||||
|
client.close()
|
||||||
|
|
||||||
|
|
||||||
def add_user(username, password, name, last_name):
|
def add_user(username, password, name, last_name):
|
||||||
@@ -62,14 +74,17 @@ def add_user(username, password, name, last_name):
|
|||||||
Returns:
|
Returns:
|
||||||
bool: True if user was added successfully, False if password was too weak
|
bool: True if user was added successfully, False if password was too weak
|
||||||
"""
|
"""
|
||||||
client = MongoClient('localhost', 27017)
|
|
||||||
db = client['Invario_Website']
|
|
||||||
users = db['users']
|
|
||||||
if not check_password_strength(password):
|
if not check_password_strength(password):
|
||||||
return False
|
return False
|
||||||
users.insert_one({'Username': username, 'Password': hashing(password), 'Admin': False, 'active_ausleihung': None, 'name': name, 'last_name': last_name})
|
|
||||||
client.close()
|
client = None
|
||||||
return True
|
try:
|
||||||
|
client, users = _get_users_collection()
|
||||||
|
users.insert_one({'Username': username, 'Password': hashing(password), 'Admin': False, 'active_ausleihung': None, 'name': name, 'last_name': last_name})
|
||||||
|
return True
|
||||||
|
finally:
|
||||||
|
if client:
|
||||||
|
client.close()
|
||||||
|
|
||||||
|
|
||||||
def make_admin(username):
|
def make_admin(username):
|
||||||
@@ -82,12 +97,14 @@ def make_admin(username):
|
|||||||
Returns:
|
Returns:
|
||||||
bool: True if user was promoted successfully
|
bool: True if user was promoted successfully
|
||||||
"""
|
"""
|
||||||
client = MongoClient('localhost', 27017)
|
client = None
|
||||||
db = client['Invario_Website']
|
try:
|
||||||
users = db['users']
|
client, users = _get_users_collection()
|
||||||
users.update_one({'Username': username}, {'$set': {'Admin': True}})
|
users.update_one({'Username': username}, {'$set': {'Admin': True}})
|
||||||
client.close()
|
return True
|
||||||
return True
|
finally:
|
||||||
|
if client:
|
||||||
|
client.close()
|
||||||
|
|
||||||
def remove_admin(username):
|
def remove_admin(username):
|
||||||
"""
|
"""
|
||||||
@@ -99,12 +116,14 @@ def remove_admin(username):
|
|||||||
Returns:
|
Returns:
|
||||||
bool: True if user was demoted successfully
|
bool: True if user was demoted successfully
|
||||||
"""
|
"""
|
||||||
client = MongoClient('localhost', 27017)
|
client = None
|
||||||
db = client['Invario_Website']
|
try:
|
||||||
users = db['users']
|
client, users = _get_users_collection()
|
||||||
users.update_one({'Username': username}, {'$set': {'Admin': False}})
|
users.update_one({'Username': username}, {'$set': {'Admin': False}})
|
||||||
client.close()
|
return True
|
||||||
return True
|
finally:
|
||||||
|
if client:
|
||||||
|
client.close()
|
||||||
|
|
||||||
def get_user(username):
|
def get_user(username):
|
||||||
"""
|
"""
|
||||||
@@ -116,12 +135,13 @@ def get_user(username):
|
|||||||
Returns:
|
Returns:
|
||||||
dict: User document or None if not found
|
dict: User document or None if not found
|
||||||
"""
|
"""
|
||||||
client = MongoClient('localhost', 27017)
|
client = None
|
||||||
db = client['Invario_Website']
|
try:
|
||||||
users = db['users']
|
client, users = _get_users_collection()
|
||||||
users_return = users.find_one({'Username': username})
|
return users.find_one({'Username': username})
|
||||||
client.close()
|
finally:
|
||||||
return users_return
|
if client:
|
||||||
|
client.close()
|
||||||
|
|
||||||
|
|
||||||
def check_admin(username):
|
def check_admin(username):
|
||||||
@@ -134,12 +154,14 @@ def check_admin(username):
|
|||||||
Returns:
|
Returns:
|
||||||
bool: True if user is an administrator, False otherwise
|
bool: True if user is an administrator, False otherwise
|
||||||
"""
|
"""
|
||||||
client = MongoClient('localhost', 27017)
|
client = None
|
||||||
db = client['Invario_Website']
|
try:
|
||||||
users = db['users']
|
client, users = _get_users_collection()
|
||||||
user = users.find_one({'Username': username})
|
user = users.find_one({'Username': username})
|
||||||
client.close()
|
return user and user.get('Admin', False)
|
||||||
return user and user.get('Admin', False)
|
finally:
|
||||||
|
if client:
|
||||||
|
client.close()
|
||||||
|
|
||||||
def delete_user(username):
|
def delete_user(username):
|
||||||
"""
|
"""
|
||||||
@@ -152,20 +174,16 @@ def delete_user(username):
|
|||||||
Returns:
|
Returns:
|
||||||
bool: True if user was deleted successfully, False otherwise
|
bool: True if user was deleted successfully, False otherwise
|
||||||
"""
|
"""
|
||||||
client = MongoClient('localhost', 27017)
|
client = None
|
||||||
db = client['Invario_Website']
|
try:
|
||||||
users = db['users']
|
client, users = _get_users_collection()
|
||||||
result = users.delete_one({'username': username})
|
result = users.delete_one({'username': username})
|
||||||
client.close()
|
if result.deleted_count == 0:
|
||||||
if result.deleted_count == 0:
|
result = users.delete_one({'Username': username})
|
||||||
# Try with different field name
|
return result.deleted_count > 0
|
||||||
client = MongoClient('localhost', 27017)
|
finally:
|
||||||
db = client['Inventarsystem']
|
if client:
|
||||||
users = db['users']
|
client.close()
|
||||||
result = users.delete_one({'Username': username})
|
|
||||||
client.close()
|
|
||||||
|
|
||||||
return result.deleted_count > 0
|
|
||||||
|
|
||||||
def get_name(username):
|
def get_name(username):
|
||||||
"""
|
"""
|
||||||
@@ -174,12 +192,14 @@ def get_name(username):
|
|||||||
Returns:
|
Returns:
|
||||||
str: String of name
|
str: String of name
|
||||||
"""
|
"""
|
||||||
client = MongoClient('localhost', 27017)
|
client = None
|
||||||
db = client['Invario_Website']
|
try:
|
||||||
users = db['users']
|
client, users = _get_users_collection()
|
||||||
user = users.find_one({'Username': username})
|
user = users.find_one({'Username': username}) or {}
|
||||||
name = user.get("name")
|
return user.get("name")
|
||||||
return name
|
finally:
|
||||||
|
if client:
|
||||||
|
client.close()
|
||||||
|
|
||||||
def get_last_name(username):
|
def get_last_name(username):
|
||||||
"""
|
"""
|
||||||
@@ -188,12 +208,14 @@ def get_last_name(username):
|
|||||||
Returns:
|
Returns:
|
||||||
str: String of last_name
|
str: String of last_name
|
||||||
"""
|
"""
|
||||||
client = MongoClient('localhost', 27017)
|
client = None
|
||||||
db = client['Invario_Website']
|
try:
|
||||||
users = db['users']
|
client, users = _get_users_collection()
|
||||||
user = users.find_one({'Username': username})
|
user = users.find_one({'Username': username}) or {}
|
||||||
name = user.get("last_name")
|
return user.get("last_name")
|
||||||
return name
|
finally:
|
||||||
|
if client:
|
||||||
|
client.close()
|
||||||
|
|
||||||
|
|
||||||
def get_all_users():
|
def get_all_users():
|
||||||
@@ -205,9 +227,7 @@ def get_all_users():
|
|||||||
list: List of all user documents
|
list: List of all user documents
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
client = MongoClient('localhost', 27017)
|
client, users = _get_users_collection()
|
||||||
db = client['Invario_Website']
|
|
||||||
users = db['users']
|
|
||||||
all_users = list(users.find())
|
all_users = list(users.find())
|
||||||
client.close()
|
client.close()
|
||||||
return all_users
|
return all_users
|
||||||
@@ -228,10 +248,8 @@ def update_password(username, new_password):
|
|||||||
try:
|
try:
|
||||||
if not check_password_strength(new_password):
|
if not check_password_strength(new_password):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
client = MongoClient('localhost', 27017)
|
client, users = _get_users_collection()
|
||||||
db = client['Invario_Website']
|
|
||||||
users = db['users']
|
|
||||||
|
|
||||||
# Hash the new password
|
# Hash the new password
|
||||||
hashed_password = hashing(new_password)
|
hashed_password = hashing(new_password)
|
||||||
@@ -261,9 +279,7 @@ def update_user_name(username, name, last_name):
|
|||||||
bool: True if updated successfully, False otherwise
|
bool: True if updated successfully, False otherwise
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
client = MongoClient('localhost', 27017)
|
client, users = _get_users_collection()
|
||||||
db = client['Invario_Website']
|
|
||||||
users = db['users']
|
|
||||||
|
|
||||||
result = users.update_one(
|
result = users.update_one(
|
||||||
{'Username': username},
|
{'Username': username},
|
||||||
|
|||||||
Reference in New Issue
Block a user