changes to the encryption to the users and Items

This commit is contained in:
2026-07-31 15:15:09 +02:00
parent 237788af96
commit 9b12d9a3d0
2 changed files with 593 additions and 1026 deletions
+70 -380
View File
@@ -19,6 +19,7 @@ Collection Structure:
- Status fields: Verfuegbar, User (if currently borrowed)
"""
from bson.objectid import ObjectId
from bson.errors import InvalidId
import datetime
import Web.modules.database.settings as cfg
from Web.modules.database.settings import MongoClient
@@ -101,37 +102,12 @@ def add_item(name, ort, beschreibung, images=None, filter=None, filter2=None, fi
isbn=None, item_type='general', library_category=None, is_library=False):
"""
Add a new item to the inventory.
Args:
name (str): Name of the item
ort (str): Location of the item
beschreibung (str): Description of the item
images (list, optional): List of image filenames for the item
filter (str, optional): Primary filter/category for the item
filter2 (str, optional): Secondary filter/category for the item
filter3 (str, optional): Tertiary filter/category for the item
ansch_jahr (int, optional): Year of acquisition
ansch_kost (float, optional): Cost of acquisition
code_4 (str, optional): 4-digit identification code
reservierbar (bool, optional): Whether the item can be reserved in advance
series_group_id (str, optional): Shared group id for same-type batch items
series_count (int, optional): Total items in the created batch
series_position (int, optional): Position inside the batch (1-based)
is_grouped_sub_item (bool, optional): Whether this item is hidden as sub-item
parent_item_id (str, optional): Parent item id if this is a sub-item
isbn (str, optional): ISBN for books or media items
item_type (str, optional): Type of the item (e.g., 'general', 'book', 'cd')
library_category (str, optional): Library category for the item
Returns:
ObjectId: ID of the new item or None if failed
"""
try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client:
db = client[cfg.MONGODB_DB]
items = db['items']
# Set default values for optional parameters
if images is None:
images = []
@@ -161,10 +137,7 @@ def add_item(name, ort, beschreibung, images=None, filter=None, filter2=None, fi
'LastUpdated': datetime.datetime.now()
}
result = items.insert_one(item)
item_id = result.inserted_id
client.close()
return item_id
return result.inserted_id
except Exception as e:
print(f"Error adding item: {e}")
return None
@@ -173,15 +146,9 @@ def add_item(name, ort, beschreibung, images=None, filter=None, filter2=None, fi
def remove_item(id):
"""
Soft-delete an item from the inventory.
Args:
id (str): ID of the item to remove
Returns:
bool: True if successful, False otherwise
"""
try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client:
db = client[cfg.MONGODB_DB]
items = db['items']
result = items.update_one(
@@ -193,7 +160,6 @@ def remove_item(id):
'Verfuegbar': False,
}}
)
client.close()
return result.modified_count > 0
except Exception as e:
print(f"Error removing item: {e}")
@@ -203,29 +169,19 @@ def remove_item(id):
def get_group_item_ids(id):
"""
Resolve all item ids that belong to the same grouped series as the given item.
For non-grouped items, this returns only the provided id.
Args:
id (str): ID of any item in the group
Returns:
list[str]: All related item IDs (including the parent item)
"""
try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client:
db = client[cfg.MONGODB_DB]
items = db['items']
base_item = items.find_one(_active_record_query({'_id': ObjectId(id)}))
if not base_item:
client.close()
return []
resolved_ids = set()
# Prefer SeriesGroupId because it represents the full logical group.
series_group_id = base_item.get('SeriesGroupId')
if series_group_id:
for group_item in items.find(_active_record_query({'SeriesGroupId': series_group_id}), {'_id': 1}):
resolved_ids.add(str(group_item['_id']))
@@ -241,7 +197,6 @@ def get_group_item_ids(id):
for child in items.find(_active_record_query({'ParentItemId': str(base_item['_id']), 'IsGroupedSubItem': True}), {'_id': 1}):
resolved_ids.add(str(child['_id']))
client.close()
return list(resolved_ids)
except Exception as e:
print(f"Error resolving group item IDs: {e}")
@@ -251,7 +206,7 @@ def get_group_item_ids(id):
def update_item(id, name, ort, beschreibung, images, verfuegbar, filter1, filter2, filter3,
ansch_jahr, ansch_kost, code_4, reservierbar, isbn=None, item_type='general'):
try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client:
db = client[cfg.MONGODB_DB]
items = db['items']
@@ -292,26 +247,18 @@ def update_item(id, name, ort, beschreibung, images, verfuegbar, filter1, filter
{'$set': shared_update}
)
client.close()
return True
except Exception as e:
print(f"Error updating item: {e}")
return False
def update_item_status(id, verfuegbar, user=None):
"""
Update the availability status of an inventory item.
Args:
id (str): ID of the item to update
verfuegbar (bool): New availability status
user (str, optional): Username of person who borrowed the item
Returns:
bool: True if successful, False otherwise
"""
try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client:
db = client[cfg.MONGODB_DB]
items = db['items']
@@ -325,7 +272,6 @@ def update_item_status(id, verfuegbar, user=None):
if user is not None:
update_data['User'] = dp.encrypt_text(user)
elif verfuegbar:
# If item is being marked as available, clear the user field
update_query['$unset'] = {'User': ""}
result = items.update_one(
@@ -333,7 +279,6 @@ def update_item_status(id, verfuegbar, user=None):
update_query
)
client.close()
return result.modified_count > 0
except Exception as e:
print(f"Error updating item status: {e}")
@@ -343,16 +288,9 @@ def update_item_status(id, verfuegbar, user=None):
def update_item_exemplare_status(id, exemplare_status):
"""
Update the exemplar status of an inventory item.
Args:
id (str): ID of the item to update
exemplare_status (list): List of status objects for each exemplar
Returns:
bool: True if successful, False otherwise
"""
try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client:
db = client[cfg.MONGODB_DB]
items = db['items']
@@ -366,7 +304,6 @@ def update_item_exemplare_status(id, exemplare_status):
{'$set': update_data}
)
client.close()
return result.modified_count > 0
except Exception as e:
print(f"Error updating exemplar status: {e}")
@@ -376,33 +313,20 @@ def update_item_exemplare_status(id, exemplare_status):
def is_code_unique(code_4, exclude_id=None):
"""
Check if a given code is unique (not used by any other item).
Args:
code_4 (str): The code to check
exclude_id (str, optional): ID of item to exclude from the check (for edit operations)
Returns:
bool: True if code is unique, False if already in use
"""
if not code_4 or code_4.strip() == "":
# Empty codes are not considered unique
return False
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client:
db = client[cfg.MONGODB_DB]
items = db['items']
# Build query to find items with this code
query = {'Code_4': code_4, 'Deleted': {'$ne': True}}
# If we're editing an item, exclude it from the uniqueness check
if exclude_id:
query['_id'] = {'$ne': ObjectId(exclude_id)}
# Check if any items with this code exist
count = items.count_documents(query)
client.close()
return count == 0
@@ -411,20 +335,18 @@ def is_code_unique(code_4, exclude_id=None):
def get_items():
"""
Retrieve all inventory items.
Returns:
list: List of all inventory item documents with string IDs
"""
try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client:
db = client[cfg.MONGODB_DB]
items = db['items']
items_return = items.find(_active_record_query(_non_library_query()))
items_list = []
for item in items_return:
item['_id'] = str(item['_id'])
items_list.append(item)
client.close()
return items_list
except Exception as e:
print(f"Error retrieving items: {e}")
@@ -434,20 +356,18 @@ def get_items():
def get_available_items():
"""
Retrieve all available inventory items.
Returns:
list: List of available inventory item documents with string IDs
"""
try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client:
db = client[cfg.MONGODB_DB]
items = db['items']
items_return = items.find(_active_record_query(_non_library_query({'Verfuegbar': True})))
items_list = []
for item in items_return:
item['_id'] = str(item['_id'])
items_list.append(item)
client.close()
return items_list
except Exception as e:
print(f"Error retrieving available items: {e}")
@@ -457,25 +377,24 @@ def get_available_items():
def get_borrowed_items():
"""
Retrieve all currently borrowed inventory items.
Returns:
list: List of borrowed inventory item documents with string IDs
"""
try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client:
db = client[cfg.MONGODB_DB]
items = db['items']
items_return = items.find(_active_record_query(_non_library_query({'Verfuegbar': False})))
items_list = []
for item in items_return:
item['_id'] = str(item['_id'])
items_list.append(item)
client.close()
return items_list
except Exception as e:
print(f"Error retrieving borrowed items: {e}")
return []
def get_item(id, decrypt=True):
"""
Retrieve an inventory item by ID, with optional decryption.
@@ -485,11 +404,12 @@ def get_item(id, decrypt=True):
return None
try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client:
db = client[cfg.MONGODB_DB]
items = db['items']
query = _active_record_query({'_id': item_id})
item = items.find_one(query)
if item:
item['_id'] = str(item['_id'])
if decrypt:
@@ -499,22 +419,16 @@ def get_item(id, decrypt=True):
print(f"Error retrieving item {id}: {e}")
return None
def get_item_by_name(name):
"""
Retrieve a specific inventory item by its name.
Args:
name (str): Name of the item to retrieve
Returns:
dict: The inventory item document or None if not found
"""
try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client:
db = client[cfg.MONGODB_DB]
items = db['items']
item = items.find_one(_active_record_query({'Name': name}))
client.close()
item = items.find_one(_active_record_query(_non_library_query({'Name': name})))
return item
except Exception as e:
print(f"Error retrieving item by name: {e}")
@@ -524,19 +438,12 @@ def get_item_by_name(name):
def get_items_by_filter(filter_value):
"""
Retrieve inventory items matching a specific filter/category.
Args:
filter_value (str): Filter value to search for
Returns:
list: List of items matching the filter in primary, secondary, or tertiary category
"""
try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client:
db = client[cfg.MONGODB_DB]
items = db['items']
# Use $or to find matches in any filter field
query = _active_record_query(_non_library_query({
'$or': [
{'Filter': filter_value},
@@ -546,9 +453,6 @@ def get_items_by_filter(filter_value):
}))
results = list(items.find(query))
client.close()
# Convert ObjectId to string
for item in results:
item['_id'] = str(item['_id'])
@@ -561,29 +465,24 @@ def get_items_by_filter(filter_value):
def get_filters():
"""
Retrieve all unique filter/category values from the inventory.
Returns:
list: Combined list of all primary, secondary and tertiary filter values
"""
try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client:
db = client[cfg.MONGODB_DB]
items = db['items']
non_library = _active_record_query(_non_library_query())
filters = items.distinct('Filter', non_library)
filters2 = items.distinct('Filter2', non_library)
filters3 = items.distinct('Filter3', non_library)
# Combine filters and remove None/empty values
all_filters = [f for f in filters + filters2 + filters3 if f]
# Remove duplicates while preserving order
unique_filters = []
for f in all_filters:
if f not in unique_filters:
unique_filters.append(f)
client.close()
return unique_filters
except Exception as e:
print(f"Error retrieving filters: {e}")
@@ -591,20 +490,13 @@ def get_filters():
def get_primary_filters():
"""
Retrieve all unique primary filter values.
Returns:
list: List of all primary filter values
"""
"""Retrieve all unique primary filter values."""
try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client:
db = client[cfg.MONGODB_DB]
items = db['items']
filters = [f for f in items.distinct('Filter', _active_record_query(_non_library_query())) if f]
client.close()
# Add predefined values
predefined = get_predefined_filter_values(1)
return sorted(list(set(filters + predefined)))
except Exception as e:
@@ -613,20 +505,13 @@ def get_primary_filters():
def get_secondary_filters():
"""
Retrieve all unique secondary filter values.
Returns:
list: List of all secondary filter values
"""
"""Retrieve all unique secondary filter values."""
try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client:
db = client[cfg.MONGODB_DB]
items = db['items']
filters = [f for f in items.distinct('Filter2', _active_record_query(_non_library_query())) if f]
client.close()
# Add predefined values
predefined = get_predefined_filter_values(2)
return sorted(list(set(filters + predefined)))
except Exception as e:
@@ -635,20 +520,13 @@ def get_secondary_filters():
def get_tertiary_filters():
"""
Retrieve all unique tertiary filter values.
Returns:
list: List of all tertiary filter values
"""
"""Retrieve all unique tertiary filter values."""
try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client:
db = client[cfg.MONGODB_DB]
items = db['items']
filters = [f for f in items.distinct('Filter3', _active_record_query(_non_library_query())) if f]
client.close()
# Add predefined values
predefined = get_predefined_filter_values(3)
return sorted(list(set(filters + predefined)))
except Exception as e:
@@ -659,24 +537,16 @@ def get_tertiary_filters():
def get_item_by_code_4(code_4):
"""
Retrieve inventory items matching a specific 4-digit code.
Args:
code_4 (str): 4-digit code to search for
Returns:
list: List of items matching the code
"""
try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client:
db = client[cfg.MONGODB_DB]
items = db['items']
results = list(items.find(_active_record_query(_non_library_query({"Code_4": code_4}))))
# Convert ObjectId to string
for item in results:
item['_id'] = str(item['_id'])
client.close()
return results
except Exception as e:
print(f"Error retrieving item by code: {e}")
@@ -688,19 +558,13 @@ def get_item_by_code_4(code_4):
def unstuck_item(id):
"""
Remove all borrowing records for a specific item to reset its status.
Used to fix problematic or stuck items.
Args:
id (str): ID of the item to unstick
Returns:
bool: True if successful, False otherwise
"""
try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client:
db = client[cfg.MONGODB_DB]
ausleihungen = db['ausleihungen']
result = ausleihungen.update_many(
ausleihungen.update_many(
{'Item': id, 'Status': {'$nin': ['cancelled', 'deleted']}},
{'$set': {
'Status': 'cancelled',
@@ -709,7 +573,6 @@ def unstuck_item(id):
}}
)
# Also reset the item status
items = db['items']
items.update_one(
{'_id': ObjectId(id)},
@@ -721,8 +584,6 @@ def unstuck_item(id):
'$unset': {'User': ""}
}
)
client.close()
return True
except Exception as e:
print(f"Error unsticking item: {e}")
@@ -732,128 +593,79 @@ def unstuck_item(id):
def get_predefined_filter_values(filter_num):
"""
Get predefined values for a specific filter.
Args:
filter_num (int): Filter number (1 for Unterrichtsfach, 2 for Jahrgangsstufe)
Returns:
list: List of predefined filter values
"""
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client:
db = client[cfg.MONGODB_DB]
# Use a dedicated collection for filter presets
filter_presets = db['filter_presets']
# Find the document for the specified filter
filter_doc = filter_presets.find_one({'filter_num': filter_num})
client.close()
if filter_doc and 'values' in filter_doc:
# Sort values alphabetically
return sorted(filter_doc['values'])
else:
# Create empty document if it doesn't exist
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
db = client[cfg.MONGODB_DB]
filter_presets = db['filter_presets']
filter_presets.update_one(
{'filter_num': filter_num},
{'$set': {'values': []}},
upsert=True
)
client.close()
return []
def add_predefined_filter_value(filter_num, value):
"""
Add a new predefined value to a filter.
Args:
filter_num (int): Filter number (1 for Unterrichtsfach, 2 for Jahrgangsstufe)
value (str): Value to add
Returns:
bool: True if value was added, False if it already existed
"""
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client:
db = client[cfg.MONGODB_DB]
filter_presets = db['filter_presets']
# Check if value already exists
filter_doc = filter_presets.find_one({
'filter_num': filter_num,
'values': value
})
if filter_doc:
# Value already exists
client.close()
return False
# Add the value to the filter
result = filter_presets.update_one(
{'filter_num': filter_num},
{'$push': {'values': value}},
upsert=True
)
client.close()
return result.modified_count > 0 or result.upserted_id is not None
def remove_predefined_filter_value(filter_num, value):
"""
Remove a predefined value from a filter.
Args:
filter_num (int): Filter number (1 for Unterrichtsfach, 2 for Jahrgangsstufe)
value (str): Value to remove
Returns:
bool: True if value was removed, False otherwise
"""
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client:
db = client[cfg.MONGODB_DB]
filter_presets = db['filter_presets']
# Remove the value from the filter
result = filter_presets.update_one(
{'filter_num': filter_num},
{'$pull': {'values': value}}
)
client.close()
return result.modified_count > 0
def edit_predefined_filter_value(filter_num, old_value, new_value):
"""
Edit a predefined value from a filter and update all matching items.
Args:
filter_num (int): Filter number (1 for Unterrichtsfach, 2 for Jahrgangsstufe)
old_value (str): Value to replace
new_value (str): New value
Returns:
bool: True if value was updated, False otherwise
"""
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client:
db = client[cfg.MONGODB_DB]
filter_presets = db['filter_presets']
# Check if the new value already exists
existing = filter_presets.find_one({
'filter_num': filter_num,
'values': new_value
})
if existing and old_value != new_value:
client.close()
return False
# Update the value in the filter
result = filter_presets.update_one(
{'filter_num': filter_num, 'values': old_value},
{'$set': {'values.$': new_value}}
@@ -863,41 +675,42 @@ def edit_predefined_filter_value(filter_num, old_value, new_value):
items = db['items']
filter_field = 'Filter' if filter_num == 1 else f'Filter{filter_num}'
# Also update all items that use this filter
items.update_many(
{filter_field: old_value},
{'$set': {filter_field: new_value}}
)
client.close()
return result.modified_count > 0
def get_filter_names():
"""Get customized filter category names."""
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client:
db = client[cfg.MONGODB_DB]
names_doc = db.settings.find_one({'setting_type': 'filter_names'})
client.close()
if names_doc and 'names' in names_doc:
return names_doc['names']
return {
'1': 'Fach/Kategorie',
'2': 'System/Bereich',
'3': 'Typ/Art'
}
def set_filter_name(filter_num, name):
"""Set custom name for a filter category."""
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client:
db = client[cfg.MONGODB_DB]
names = get_filter_names()
names[str(filter_num)] = name
db.settings.update_one(
{'setting_type': 'filter_names'},
{'$set': {'names': names}},
upsert=True
)
client.close()
return True
@@ -906,33 +719,25 @@ def set_filter_name(filter_num, name):
def get_predefined_locations():
"""
Get list of all predefined locations/placement options.
Returns:
list: List of predefined location strings
"""
try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client:
db = client[cfg.MONGODB_DB]
# Check if settings collection exists, create if not
if 'settings' not in db.list_collection_names():
db.create_collection('settings')
# Get settings document or create if it doesn't exist
settings_collection = db['settings']
location_settings = settings_collection.find_one({'setting_type': 'predefined_locations'})
if not location_settings:
# Create default settings document if it doesn't exist
settings_collection.insert_one({
'setting_type': 'predefined_locations',
'locations': []
})
return []
# Return the predefined locations
locations = location_settings.get('locations', [])
client.close()
return sorted(locations)
except Exception as e:
@@ -943,12 +748,6 @@ def get_predefined_locations():
def add_predefined_location(location):
"""
Add a new predefined location.
Args:
location (str): Location to add
Returns:
bool: True if added successfully, False if already exists
"""
if not location or not isinstance(location, str):
return False
@@ -958,35 +757,27 @@ def add_predefined_location(location):
return False
try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client:
db = client[cfg.MONGODB_DB]
settings_collection = db['settings']
# Check if settings document exists, create if not
location_settings = settings_collection.find_one({'setting_type': 'predefined_locations'})
if not location_settings:
# Create with the new location
settings_collection.insert_one({
'setting_type': 'predefined_locations',
'locations': [location]
})
client.close()
return True
# Check if location already exists (case-insensitive)
current_locations = location_settings.get('locations', [])
if any(loc.lower() == location.lower() for loc in current_locations):
client.close()
return False
# Add the new location
settings_collection.update_one(
{'setting_type': 'predefined_locations'},
{'$push': {'locations': location}}
)
client.close()
return True
except Exception as e:
@@ -997,18 +788,12 @@ def add_predefined_location(location):
def remove_predefined_location(location):
"""
Remove a predefined location.
Args:
location (str): Location to remove
Returns:
bool: True if removed successfully
"""
if not location:
return False
try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client:
db = client[cfg.MONGODB_DB]
settings_collection = db['settings']
@@ -1016,8 +801,6 @@ def remove_predefined_location(location):
{'setting_type': 'predefined_locations'},
{'$pull': {'locations': location}}
)
client.close()
return result.modified_count > 0
except Exception as e:
@@ -1028,129 +811,36 @@ def remove_predefined_location(location):
def update_item_next_appointment(item_id, appointment_data):
"""
Update an item with information about its next scheduled appointment.
Args:
item_id (str): ID of the item to update
appointment_data (dict): Appointment information containing:
- date: Date of the appointment
- start_period: Start period number
- end_period: End period number
- user: Username who scheduled the appointment
- notes: Optional notes
- appointment_id: ID of the appointment booking
Returns:
bool: True if successful, False otherwise
"""
try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client:
db = client[cfg.MONGODB_DB]
items = db['items']
# Format the appointment data for storage
# Ensure date is a datetime object for MongoDB storage
appointment_date = appointment_data['date']
if isinstance(appointment_date, datetime.date) and not isinstance(appointment_date, datetime.datetime):
# Convert date to datetime for MongoDB compatibility
appointment_date = datetime.datetime.combine(appointment_date, datetime.time())
next_appointment = {
'date': appointment_date,
'end_date': appointment_data.get('end_date', appointment_date),
'start_period': appointment_data['start_period'],
'end_period': appointment_data['end_period'],
'user': dp.encrypt_text(appointment_data['user']),
'notes': appointment_data.get('notes', ''),
'appointment_id': appointment_data['appointment_id'],
'scheduled_at': datetime.datetime.now()
if appointment_data is None:
update_query = {
'$unset': {'NextAppointment': ""},
'$set': {'LastUpdated': datetime.datetime.now()}
}
else:
data_to_save = appointment_data.copy()
update_data = {
'NextAppointment': next_appointment,
if 'user' in data_to_save and data_to_save['user']:
data_to_save['user'] = dp.encrypt_text(data_to_save['user'])
update_query = {
'$set': {
'NextAppointment': data_to_save,
'LastUpdated': datetime.datetime.now()
}
}
result = items.update_one(
{'_id': ObjectId(item_id)},
{'$set': update_data}
update_query
)
client.close()
return result.modified_count > 0
except Exception as e:
print(f"Error updating item next appointment: {e}")
return False
def clear_item_next_appointment(item_id):
"""
Clear the next appointment information from an item.
Args:
item_id (str): ID of the item to update
Returns:
bool: True if successful, False otherwise
"""
try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
db = client[cfg.MONGODB_DB]
items = db['items']
result = items.update_one(
{'_id': ObjectId(item_id)},
{'$unset': {'NextAppointment': ""}, '$set': {'LastUpdated': datetime.datetime.now()}}
)
client.close()
return result.modified_count > 0
except Exception as e:
print(f"Error clearing item next appointment: {e}")
return False
def get_items_with_appointments():
"""
Retrieve all items that have scheduled appointments.
Returns:
list: List of items with NextAppointment field
"""
try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
db = client[cfg.MONGODB_DB]
items = db['items']
items_return = items.find({'NextAppointment': {'$exists': True}, 'Deleted': {'$ne': True}})
items_list = []
for item in items_return:
item['_id'] = str(item['_id'])
items_list.append(item)
client.close()
return items_list
except Exception as e:
print(f"Error retrieving items with appointments: {e}")
return []
def get_current_status(item_id, decrypt=True):
"""
Retrieve the current status of an item, decrypting the user field if present.
"""
oid = _to_object_id(item_id)
if not oid:
return None
try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
db = client[cfg.MONGODB_DB]
items = db['items']
item = items.find_one({'_id': oid}, {'Verfuegbar': 1, 'User': 1})
if item:
item['_id'] = str(item['_id'])
if decrypt:
decrypt_item_user_data(item)
return item
return None
except Exception as e:
print(f"Error retrieving current status for item {item_id}: {e}")
return None
+108 -231
View File
@@ -110,13 +110,6 @@ def build_username_from_name(first_name, last_name=''):
"""
Build a deterministic username abbreviation from first and last name.
Uses 3 letters from each name and stores it lowercase.
Args:
first_name (str): First name
last_name (str): Last name (optional)
Returns:
str: Generated username
"""
alias = build_name_synonym(first_name, last_name)
return alias.lower()
@@ -325,7 +318,6 @@ def get_effective_permissions(username):
return build_default_permission_payload('full_access')
preset_key = user.get('PermissionPreset')
print(preset_key)
payload = build_default_permission_payload(preset_key)
payload['actions'] = _normalize_bool_map(user.get('ActionPermissions', {}), payload['actions'])
payload['pages'] = _normalize_bool_map(user.get('PagePermissions', {}), payload['pages'])
@@ -353,10 +345,10 @@ def update_user_permissions(username, preset_key, action_permissions=None, page_
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
db = _get_tenant_db(client)
users = db['users']
result = users.update_one({'Username': username}, {'$set': update_data})
result = users.update_one({'Username': dp.encrypt_text(username)}, {'$set': update_data})
if result.matched_count == 0:
result = users.update_one({'username': username}, {'$set': update_data})
result = users.update_one({'username': dp.encrypt_text(username)}, {'$set': update_data})
client.close()
return result.matched_count > 0
@@ -368,7 +360,7 @@ def get_favorites(username):
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
db = _get_tenant_db(client)
users = db['users']
user = users.find_one({'Username': username}) or users.find_one({'username': username})
user = users.find_one({'Username': dp.encrypt_text(username)}) or users.find_one({'username': dp.encrypt_text(username)})
client.close()
if not user:
return []
@@ -383,7 +375,7 @@ def add_favorite(username, item_id):
db = _get_tenant_db(client)
users = db['users']
users.update_one(
{'$or': [{'Username': username}, {'username': username}]},
{'$or': [{'Username': dp.encrypt_text(username)}, {'username': dp.encrypt_text(username)}]},
{'$addToSet': {'favorites': ObjectId(item_id)}}
)
client.close()
@@ -398,7 +390,7 @@ def remove_favorite(username, item_id):
db = _get_tenant_db(client)
users = db['users']
users.update_one(
{'$or': [{'Username': username}, {'username': username}]},
{'$or': [{'Username': dp.encrypt_text(username)}, {'username': dp.encrypt_text(username)}]},
{'$pull': {'favorites': ObjectId(item_id)}}
)
client.close()
@@ -407,16 +399,9 @@ def remove_favorite(username, item_id):
return False
def check_password_strength(password):
"""
Check if a password meets minimum security requirements.
Args:
password (str): Password to check
Returns:
bool: True if password is strong enough, False otherwise
"""
if password is None:
return False
@@ -437,18 +422,14 @@ def check_password_strength(password):
def hashing(password, salt=None):
"""
Hasht ein Passwort mit scrypt.
- Wenn kein Salt übergeben wird, wird ein sicherer, zufälliger Salt generiert (für neue Passwörter).
- Format für neue Hashes: v1$<salt_hex>$<hash_hex>
"""
password_bytes = password.encode('utf-8') # Explizit UTF-8 für Plattformunabhängigkeit
password_bytes = password.encode('utf-8')
if salt is None:
# Neuer Benutzer / Passwortänderung -> Dynamischer Salt
random_salt = os.urandom(16)
hashed = hashlib.scrypt(password_bytes, salt=random_salt, n=16384, r=8, p=1)
return f"v1${random_salt.hex()}${hashed.hex()}"
else:
# Bestehender Benutzer (wird zur Verifizierung aufgerufen)
hashed = hashlib.scrypt(password_bytes, salt=salt, n=16384, r=8, p=1)
return hashed.hex()
@@ -456,25 +437,20 @@ def hashing(password, salt=None):
def verify_password(provided_password, stored_password_string):
"""
Verifiziert ein Passwort gegen einen gespeicherten Hash-String.
Unterstützt das alte Format (statischer Salt) und das neue Format (v1$...).
"""
if not stored_password_string:
return False
# Überprüfung für das neue, sichere Format
if stored_password_string.startswith("v1$"):
try:
_, salt_hex, hash_hex = stored_password_string.split("$")
salt_bytes = bytes.fromhex(salt_hex)
# Berechne den Hash des eingegebenen Passworts mit dem extrahierten Salt
calculated_hash = hashing(provided_password, salt=salt_bytes)
# Timing-Attack-sicherer Vergleich
return hmac.compare_digest(calculated_hash, hash_hex)
except (ValueError, TypeError):
logger.error("Ungültiges Hash-Format in der Datenbank entdeckt.")
return False
else:
# Abwärtskompatibilität: Altes Format mit statischem Salt b'some_salt'
old_static_salt = b'some_salt'
calculated_hash = hashing(provided_password, salt=old_static_salt)
return hmac.compare_digest(calculated_hash, stored_password_string)
@@ -496,21 +472,20 @@ def check_nm_pwd(username, password):
db = client[db_name]
users = db['users']
query = {'$or': [{'Username': username}, {'username': username}]}
query = {'$or': [{'Username': dp.encrypt_text(username)}, {'username': dp.encrypt_text(username)}]}
user_record = users.find_one(query)
if user_record is None:
logger.warning("Kein Benutzer für %r in DB %r gefunden.", username, db_name)
logger.warning("Kein Benutzer für %r in DB %r gefunden.", dp.encrypt_text(username), db_name)
return None
stored_password = user_record.get('Password') or user_record.get('password')
if not verify_password(password, stored_password):
logger.warning("Falsches Passwort für Benutzer %r in DB %r.", username, db_name)
logger.warning("Falsches Passwort für Benutzer %r in DB %r.", dp.encrypt_text(username), db_name)
return None
# Automatische Migration alter Hashes auf das neue Format
if not stored_password.startswith("v1$"):
if stored_password and not stored_password.startswith("v1$"):
users.update_one({'_id': user_record['_id']}, {'$set': {'Password': hashing(password)}})
return user_record
@@ -532,40 +507,35 @@ def add_user(
):
"""
Add a new user to the database.
Args:
username (str): Username for the new user
password (str): Password for the new user
Returns:
bool: True if user was added successfully, False if password was too weak
"""
if not check_password_strength(password):
return False
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
try:
db = _get_tenant_db(client)
users = db['users']
if not check_password_strength(password):
return False
permission_defaults = build_default_permission_payload(permission_preset)
if isinstance(action_permissions, dict):
for key, value in action_permissions.items():
permission_defaults['actions'][str(key)] = bool(value)
if isinstance(page_permissions, dict):
for key, value in page_permissions.items():
permission_defaults['pages'][str(key)] = bool(value)
if permission_preset == "full_access":
can_admin_preset_based = True
else:
can_admin_preset_based = False
safe_name = name.strip() if name else ''
safe_last_name = last_name.strip() if last_name else ''
user_doc = {
'Username': username,
'Username': dp.encrypt_text(username),
'Password': hashing(password),
'Admin': can_admin_preset_based,
'Admin': (permission_preset == "full_access"),
'active_ausleihung': None,
'name': name.strip() if name else '',
'last_name': last_name.strip() if last_name else '',
'name': dp.encrypt_text(safe_name) if safe_name else '',
'last_name': dp.encrypt_text(safe_last_name) if safe_last_name else '',
'IsStudent': bool(is_student),
'PermissionPreset': permission_defaults['preset'],
'ActionPermissions': permission_defaults['actions'],
@@ -602,7 +572,7 @@ def student_card_exists(student_card_id):
def get_user_by_student_card(student_card_id):
"""Return user by student card id or None."""
"""Return user dict by student card id or None."""
normalized = normalize_student_card_id(student_card_id)
if not normalized:
return None
@@ -611,65 +581,44 @@ def get_user_by_student_card(student_card_id):
users = db['student_cards']
found_user = users.find_one({'SchülerName': normalized})
client.close()
# Do not call dp.decrypt_text() here because found_user is a MongoDB dictionary.
return found_user
def make_admin(username):
"""
Grant administrator privileges to a user.
Args:
username (str): Username of the user to promote
Returns:
bool: True if user was promoted successfully
"""
"""Grant administrator privileges to a user."""
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
db = _get_tenant_db(client)
users = db['users']
result = users.update_one({'Username': username}, {'$set': {'Admin': True}})
result = users.update_one({'Username': dp.encrypt_text(username)}, {'$set': {'Admin': True}})
if result.matched_count == 0:
result = users.update_one({'username': username}, {'$set': {'Admin': True}})
result = users.update_one({'username': dp.encrypt_text(username)}, {'$set': {'Admin': True}})
client.close()
return result.matched_count > 0
def remove_admin(username):
"""
Remove administrator privileges from a user.
Args:
username (str): Username of the user to demote
Returns:
bool: True if user was demoted successfully
"""
"""Remove administrator privileges from a user."""
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
db = _get_tenant_db(client)
users = db['users']
result = users.update_one({'Username': username}, {'$set': {'Admin': False}})
result = users.update_one({'Username': dp.encrypt_text(username)}, {'$set': {'Admin': False}})
if result.matched_count == 0:
result = users.update_one({'username': username}, {'$set': {'Admin': False}})
result = users.update_one({'username': dp.encrypt_text(username)}, {'$set': {'Admin': False}})
client.close()
return result.matched_count > 0
def get_user(username):
"""
Retrieve a specific user by username.
Args:
username (str): Username to search for
Returns:
dict: User document or None if not found
"""
"""Retrieve a specific user by username."""
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
try:
def find_in_db(database_name):
db = client[database_name]
users = db['users']
return users.find_one({'Username': username}) or users.find_one({'username': username})
return users.find_one({'Username': dp.encrypt_text(username)}) or users.find_one({'username': dp.encrypt_text(username)})
# Try current tenant first when available
tenant_db, tenant_id = _resolve_request_tenant_db()
if tenant_db:
user = find_in_db(tenant_db)
@@ -682,7 +631,6 @@ def get_user(username):
)
return None
# Fallback to default configured database
user = find_in_db(cfg.MONGODB_DB)
if user:
return user
@@ -693,146 +641,88 @@ def get_user(username):
def check_admin(username):
"""
Check if a user has administrator privileges.
Args:
username (str): Username to check
Returns:
bool: True if user is an administrator, False otherwise
"""
"""Check if a user has administrator privileges."""
user = get_user(username)
return bool(user and user.get('Admin', False))
def update_active_ausleihung(username, id_item, ausleihung):
"""
Update a user's active borrowing record.
Args:
username (str): Username of the user
id_item (str): ID of the borrowed item
ausleihung (str): ID of the borrowing record
Returns:
bool: True if successful
"""
"""Update a user's active borrowing record."""
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
db = _get_tenant_db(client)
users = db['users']
users.update_one({'Username': username}, {'$set': {'active_ausleihung': {'Item': id_item, 'Ausleihung': ausleihung}}})
result = users.update_one(
{'Username': dp.encrypt_text(username)},
{'$set': {'active_ausleihung': {'Item': id_item, 'Ausleihung': ausleihung}}}
)
if result.matched_count == 0:
users.update_one(
{'username': dp.encrypt_text(username)},
{'$set': {'active_ausleihung': {'Item': id_item, 'Ausleihung': ausleihung}}}
)
client.close()
return True
def get_active_ausleihung(username):
"""
Get a user's active borrowing record.
Args:
username (str): Username of the user
Returns:
dict: Active borrowing information or None
"""
"""Get a user's active borrowing record."""
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
db = _get_tenant_db(client)
users = db['users']
user = users.find_one({'Username': username})
return user['active_ausleihung']
user = users.find_one({'Username': dp.encrypt_text(username)}) or users.find_one({'username': dp.encrypt_text(username)})
client.close()
if not user:
return None
return user.get('active_ausleihung')
def has_active_borrowing(username):
"""
Check if a user currently has an active borrowing.
Args:
username (str): Username to check
Returns:
bool: True if user has an active borrowing, False otherwise
"""
"""Check if a user currently has an active borrowing."""
try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
db = _get_tenant_db(client)
users = db['users']
user = users.find_one({'username': username})
if not user:
user = users.find_one({'Username': username})
user = users.find_one({'username': dp.encrypt_text(username)}) or users.find_one({'Username': dp.encrypt_text(username)})
client.close()
if not user:
client.close()
return False
has_active = user.get('active_borrowing', False)
client.close()
return has_active
return user.get('active_borrowing', False)
except Exception as e:
return False
def delete_user(username):
"""
Delete a user from the database.
Administrative function for removing user accounts.
Args:
username (str): Username of the account to delete
Returns:
bool: True if user was deleted successfully, False otherwise
"""
"""Delete a user from the database."""
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
db = _get_tenant_db(client)
users = db['users']
result = users.delete_one({'username': username})
client.close()
result = users.delete_one({'username': dp.encrypt_text(username)})
if result.deleted_count == 0:
# Try with different field name
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
db = _get_tenant_db(client)
users = db['users']
result = users.delete_one({'Username': username})
client.close()
result = users.delete_one({'Username': dp.encrypt_text(username)})
client.close()
return result.deleted_count > 0
def update_active_borrowing(username, item_id, status):
"""
Update a user's active borrowing status.
Args:
username (str): Username of the user
item_id (str): ID of the borrowed item or None if returning
status (bool): True if borrowing, False if returning
Returns:
bool: True if successful, False on error
"""
"""Update a user's active borrowing status."""
try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
db = _get_tenant_db(client)
users = db['users']
result = users.update_one(
{'username': username},
{'$set': {
'active_borrowing': status,
'borrowed_item': item_id if status else None
}}
)
update_data = {'$set': {'active_borrowing': status, 'borrowed_item': item_id if status else None}}
result = users.update_one({'username': dp.encrypt_text(username)}, update_data)
if result.matched_count == 0:
result = users.update_one(
{'Username': username},
{'$set': {
'active_borrowing': status,
'borrowed_item': item_id if status else None
}}
)
result = users.update_one({'Username': dp.encrypt_text(username)}, update_data)
client.close()
return result.modified_count > 0
@@ -841,43 +731,37 @@ def update_active_borrowing(username, item_id, status):
def get_name(username):
"""
Retrieve the name that is assosiated with the username.
Returns:
str: String of name
"""
"""Retrieve the name that is associated with the username."""
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
db = _get_tenant_db(client)
users = db['users']
user = users.find_one({'Username': username})
name = user.get("name")
return name
user = users.find_one({'Username': dp.encrypt_text(username)}) or users.find_one({'username': dp.encrypt_text(username)})
client.close()
if not user or not user.get("name"):
return ""
return dp.decrypt_text(user.get("name"))
def get_last_name(username):
"""
Retrieve the last_name that is assosiated with the username.
Returns:
str: String of last_name
"""
"""Retrieve the last_name that is associated with the username."""
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
db = _get_tenant_db(client)
users = db['users']
user = users.find_one({'Username': username})
name = user.get("last_name")
return name
user = users.find_one({'Username': dp.encrypt_text(username)}) or users.find_one({'username': dp.encrypt_text(username)})
client.close()
if not user or not user.get("last_name"):
return ""
return dp.decrypt_text(user.get("last_name"))
def get_all_users():
"""
Retrieve all users from the database.
Administrative function for user management.
Returns:
list: List of all user documents
"""
"""Retrieve all users from the database."""
try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
db = _get_tenant_db(client)
@@ -888,17 +772,9 @@ def get_all_users():
except Exception as e:
return []
def update_password(username, new_password):
"""
Update a user's password with a new one.
Args:
username (str): Username of the user
new_password (str): New password to set
Returns:
bool: True if password was updated successfully, False otherwise
"""
"""Update a user's password with a new one."""
try:
if not check_password_strength(new_password):
return False
@@ -907,12 +783,15 @@ def update_password(username, new_password):
db = _get_tenant_db(client)
users = db['users']
# Hash the new password
hashed_password = hashing(new_password)
# Update the user's password
result = users.update_one(
{'Username': username},
{'Username': dp.encrypt_text(username)},
{'$set': {'Password': hashed_password}}
)
if result.matched_count == 0:
result = users.update_one(
{'username': dp.encrypt_text(username)},
{'$set': {'Password': hashed_password}}
)
@@ -922,27 +801,25 @@ def update_password(username, new_password):
print(f"Error updating password: {e}")
return False
def update_user_name(username, name, last_name):
"""
Update a user's name and last name.
Args:
username (str): Username of the user
name (str): New first name
last_name (str): New last name
Returns:
bool: True if updated successfully, False otherwise
"""
"""Update a user's name and last name."""
try:
name_alias = build_name_synonym(name, last_name)
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
db = _get_tenant_db(client)
users = db['users']
safe_name = dp.encrypt_text(name.strip()) if name else ''
safe_last_name = dp.encrypt_text(last_name.strip()) if last_name else ''
result = users.update_one(
{'Username': username},
{'$set': {'name': name_alias, 'last_name': ''}}
{'Username': dp.encrypt_text(username)},
{'$set': {'name': safe_name, 'last_name': safe_last_name}}
)
if result.matched_count == 0:
result = users.update_one(
{'username': dp.encrypt_text(username)},
{'$set': {'name': safe_name, 'last_name': safe_last_name}}
)
client.close()