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) - Status fields: Verfuegbar, User (if currently borrowed)
""" """
from bson.objectid import ObjectId from bson.objectid import ObjectId
from bson.errors import InvalidId
import datetime import datetime
import Web.modules.database.settings as cfg import Web.modules.database.settings as cfg
from Web.modules.database.settings import MongoClient 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): isbn=None, item_type='general', library_category=None, is_library=False):
""" """
Add a new item to the inventory. 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: try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client:
db = client[cfg.MONGODB_DB] db = client[cfg.MONGODB_DB]
items = db['items'] items = db['items']
# Set default values for optional parameters
if images is None: if images is None:
images = [] images = []
@@ -161,10 +137,7 @@ def add_item(name, ort, beschreibung, images=None, filter=None, filter2=None, fi
'LastUpdated': datetime.datetime.now() 'LastUpdated': datetime.datetime.now()
} }
result = items.insert_one(item) result = items.insert_one(item)
item_id = result.inserted_id return result.inserted_id
client.close()
return item_id
except Exception as e: except Exception as e:
print(f"Error adding item: {e}") print(f"Error adding item: {e}")
return None return None
@@ -173,15 +146,9 @@ def add_item(name, ort, beschreibung, images=None, filter=None, filter2=None, fi
def remove_item(id): def remove_item(id):
""" """
Soft-delete an item from the inventory. Soft-delete an item from the inventory.
Args:
id (str): ID of the item to remove
Returns:
bool: True if successful, False otherwise
""" """
try: try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client:
db = client[cfg.MONGODB_DB] db = client[cfg.MONGODB_DB]
items = db['items'] items = db['items']
result = items.update_one( result = items.update_one(
@@ -193,7 +160,6 @@ def remove_item(id):
'Verfuegbar': False, 'Verfuegbar': False,
}} }}
) )
client.close()
return result.modified_count > 0 return result.modified_count > 0
except Exception as e: except Exception as e:
print(f"Error removing item: {e}") print(f"Error removing item: {e}")
@@ -203,29 +169,19 @@ def remove_item(id):
def get_group_item_ids(id): def get_group_item_ids(id):
""" """
Resolve all item ids that belong to the same grouped series as the given item. 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: try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client:
db = client[cfg.MONGODB_DB] db = client[cfg.MONGODB_DB]
items = db['items'] items = db['items']
base_item = items.find_one(_active_record_query({'_id': ObjectId(id)})) base_item = items.find_one(_active_record_query({'_id': ObjectId(id)}))
if not base_item: if not base_item:
client.close()
return [] return []
resolved_ids = set() resolved_ids = set()
# Prefer SeriesGroupId because it represents the full logical group.
series_group_id = base_item.get('SeriesGroupId') series_group_id = base_item.get('SeriesGroupId')
if series_group_id: if series_group_id:
for group_item in items.find(_active_record_query({'SeriesGroupId': series_group_id}), {'_id': 1}): for group_item in items.find(_active_record_query({'SeriesGroupId': series_group_id}), {'_id': 1}):
resolved_ids.add(str(group_item['_id'])) 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}): for child in items.find(_active_record_query({'ParentItemId': str(base_item['_id']), 'IsGroupedSubItem': True}), {'_id': 1}):
resolved_ids.add(str(child['_id'])) resolved_ids.add(str(child['_id']))
client.close()
return list(resolved_ids) return list(resolved_ids)
except Exception as e: except Exception as e:
print(f"Error resolving group item IDs: {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, def update_item(id, name, ort, beschreibung, images, verfuegbar, filter1, filter2, filter3,
ansch_jahr, ansch_kost, code_4, reservierbar, isbn=None, item_type='general'): ansch_jahr, ansch_kost, code_4, reservierbar, isbn=None, item_type='general'):
try: try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client:
db = client[cfg.MONGODB_DB] db = client[cfg.MONGODB_DB]
items = db['items'] items = db['items']
@@ -292,26 +247,18 @@ def update_item(id, name, ort, beschreibung, images, verfuegbar, filter1, filter
{'$set': shared_update} {'$set': shared_update}
) )
client.close()
return True return True
except Exception as e: except Exception as e:
print(f"Error updating item: {e}") print(f"Error updating item: {e}")
return False return False
def update_item_status(id, verfuegbar, user=None): def update_item_status(id, verfuegbar, user=None):
""" """
Update the availability status of an inventory item. 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: try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client:
db = client[cfg.MONGODB_DB] db = client[cfg.MONGODB_DB]
items = db['items'] items = db['items']
@@ -325,7 +272,6 @@ def update_item_status(id, verfuegbar, user=None):
if user is not None: if user is not None:
update_data['User'] = dp.encrypt_text(user) update_data['User'] = dp.encrypt_text(user)
elif verfuegbar: elif verfuegbar:
# If item is being marked as available, clear the user field
update_query['$unset'] = {'User': ""} update_query['$unset'] = {'User': ""}
result = items.update_one( result = items.update_one(
@@ -333,7 +279,6 @@ def update_item_status(id, verfuegbar, user=None):
update_query update_query
) )
client.close()
return result.modified_count > 0 return result.modified_count > 0
except Exception as e: except Exception as e:
print(f"Error updating item status: {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): def update_item_exemplare_status(id, exemplare_status):
""" """
Update the exemplar status of an inventory item. 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: try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client:
db = client[cfg.MONGODB_DB] db = client[cfg.MONGODB_DB]
items = db['items'] items = db['items']
@@ -366,7 +304,6 @@ def update_item_exemplare_status(id, exemplare_status):
{'$set': update_data} {'$set': update_data}
) )
client.close()
return result.modified_count > 0 return result.modified_count > 0
except Exception as e: except Exception as e:
print(f"Error updating exemplar status: {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): def is_code_unique(code_4, exclude_id=None):
""" """
Check if a given code is unique (not used by any other item). 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() == "": if not code_4 or code_4.strip() == "":
# Empty codes are not considered unique
return False 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] db = client[cfg.MONGODB_DB]
items = db['items'] items = db['items']
# Build query to find items with this code
query = {'Code_4': code_4, 'Deleted': {'$ne': True}} query = {'Code_4': code_4, 'Deleted': {'$ne': True}}
# If we're editing an item, exclude it from the uniqueness check
if exclude_id: if exclude_id:
query['_id'] = {'$ne': ObjectId(exclude_id)} query['_id'] = {'$ne': ObjectId(exclude_id)}
# Check if any items with this code exist
count = items.count_documents(query) count = items.count_documents(query)
client.close()
return count == 0 return count == 0
@@ -411,20 +335,18 @@ def is_code_unique(code_4, exclude_id=None):
def get_items(): def get_items():
""" """
Retrieve all inventory items. Retrieve all inventory items.
Returns:
list: List of all inventory item documents with string IDs
""" """
try: try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client:
db = client[cfg.MONGODB_DB] db = client[cfg.MONGODB_DB]
items = db['items'] items = db['items']
items_return = items.find(_active_record_query(_non_library_query())) items_return = items.find(_active_record_query(_non_library_query()))
items_list = [] items_list = []
for item in items_return: for item in items_return:
item['_id'] = str(item['_id']) item['_id'] = str(item['_id'])
items_list.append(item) items_list.append(item)
client.close()
return items_list return items_list
except Exception as e: except Exception as e:
print(f"Error retrieving items: {e}") print(f"Error retrieving items: {e}")
@@ -434,20 +356,18 @@ def get_items():
def get_available_items(): def get_available_items():
""" """
Retrieve all available inventory items. Retrieve all available inventory items.
Returns:
list: List of available inventory item documents with string IDs
""" """
try: try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client:
db = client[cfg.MONGODB_DB] db = client[cfg.MONGODB_DB]
items = db['items'] items = db['items']
items_return = items.find(_active_record_query(_non_library_query({'Verfuegbar': True}))) items_return = items.find(_active_record_query(_non_library_query({'Verfuegbar': True})))
items_list = [] items_list = []
for item in items_return: for item in items_return:
item['_id'] = str(item['_id']) item['_id'] = str(item['_id'])
items_list.append(item) items_list.append(item)
client.close()
return items_list return items_list
except Exception as e: except Exception as e:
print(f"Error retrieving available items: {e}") print(f"Error retrieving available items: {e}")
@@ -457,25 +377,24 @@ def get_available_items():
def get_borrowed_items(): def get_borrowed_items():
""" """
Retrieve all currently borrowed inventory items. Retrieve all currently borrowed inventory items.
Returns:
list: List of borrowed inventory item documents with string IDs
""" """
try: try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client:
db = client[cfg.MONGODB_DB] db = client[cfg.MONGODB_DB]
items = db['items'] items = db['items']
items_return = items.find(_active_record_query(_non_library_query({'Verfuegbar': False}))) items_return = items.find(_active_record_query(_non_library_query({'Verfuegbar': False})))
items_list = [] items_list = []
for item in items_return: for item in items_return:
item['_id'] = str(item['_id']) item['_id'] = str(item['_id'])
items_list.append(item) items_list.append(item)
client.close()
return items_list return items_list
except Exception as e: except Exception as e:
print(f"Error retrieving borrowed items: {e}") print(f"Error retrieving borrowed items: {e}")
return [] return []
def get_item(id, decrypt=True): def get_item(id, decrypt=True):
""" """
Retrieve an inventory item by ID, with optional decryption. Retrieve an inventory item by ID, with optional decryption.
@@ -485,11 +404,12 @@ def get_item(id, decrypt=True):
return None return None
try: try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client:
db = client[cfg.MONGODB_DB] db = client[cfg.MONGODB_DB]
items = db['items'] items = db['items']
query = _active_record_query({'_id': item_id}) query = _active_record_query({'_id': item_id})
item = items.find_one(query) item = items.find_one(query)
if item: if item:
item['_id'] = str(item['_id']) item['_id'] = str(item['_id'])
if decrypt: if decrypt:
@@ -499,22 +419,16 @@ def get_item(id, decrypt=True):
print(f"Error retrieving item {id}: {e}") print(f"Error retrieving item {id}: {e}")
return None return None
def get_item_by_name(name): def get_item_by_name(name):
""" """
Retrieve a specific inventory item by its 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: try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client:
db = client[cfg.MONGODB_DB] db = client[cfg.MONGODB_DB]
items = db['items'] items = db['items']
item = items.find_one(_active_record_query({'Name': name})) item = items.find_one(_active_record_query(_non_library_query({'Name': name})))
client.close()
return item return item
except Exception as e: except Exception as e:
print(f"Error retrieving item by name: {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): def get_items_by_filter(filter_value):
""" """
Retrieve inventory items matching a specific filter/category. 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: try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client:
db = client[cfg.MONGODB_DB] db = client[cfg.MONGODB_DB]
items = db['items'] items = db['items']
# Use $or to find matches in any filter field
query = _active_record_query(_non_library_query({ query = _active_record_query(_non_library_query({
'$or': [ '$or': [
{'Filter': filter_value}, {'Filter': filter_value},
@@ -546,9 +453,6 @@ def get_items_by_filter(filter_value):
})) }))
results = list(items.find(query)) results = list(items.find(query))
client.close()
# Convert ObjectId to string
for item in results: for item in results:
item['_id'] = str(item['_id']) item['_id'] = str(item['_id'])
@@ -561,29 +465,24 @@ def get_items_by_filter(filter_value):
def get_filters(): def get_filters():
""" """
Retrieve all unique filter/category values from the inventory. Retrieve all unique filter/category values from the inventory.
Returns:
list: Combined list of all primary, secondary and tertiary filter values
""" """
try: try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client:
db = client[cfg.MONGODB_DB] db = client[cfg.MONGODB_DB]
items = db['items'] items = db['items']
non_library = _active_record_query(_non_library_query()) non_library = _active_record_query(_non_library_query())
filters = items.distinct('Filter', non_library) filters = items.distinct('Filter', non_library)
filters2 = items.distinct('Filter2', non_library) filters2 = items.distinct('Filter2', non_library)
filters3 = items.distinct('Filter3', 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] all_filters = [f for f in filters + filters2 + filters3 if f]
# Remove duplicates while preserving order
unique_filters = [] unique_filters = []
for f in all_filters: for f in all_filters:
if f not in unique_filters: if f not in unique_filters:
unique_filters.append(f) unique_filters.append(f)
client.close()
return unique_filters return unique_filters
except Exception as e: except Exception as e:
print(f"Error retrieving filters: {e}") print(f"Error retrieving filters: {e}")
@@ -591,20 +490,13 @@ def get_filters():
def get_primary_filters(): def get_primary_filters():
""" """Retrieve all unique primary filter values."""
Retrieve all unique primary filter values.
Returns:
list: List of all primary filter values
"""
try: try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client:
db = client[cfg.MONGODB_DB] db = client[cfg.MONGODB_DB]
items = db['items'] items = db['items']
filters = [f for f in items.distinct('Filter', _active_record_query(_non_library_query())) if f] 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) predefined = get_predefined_filter_values(1)
return sorted(list(set(filters + predefined))) return sorted(list(set(filters + predefined)))
except Exception as e: except Exception as e:
@@ -613,20 +505,13 @@ def get_primary_filters():
def get_secondary_filters(): def get_secondary_filters():
""" """Retrieve all unique secondary filter values."""
Retrieve all unique secondary filter values.
Returns:
list: List of all secondary filter values
"""
try: try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client:
db = client[cfg.MONGODB_DB] db = client[cfg.MONGODB_DB]
items = db['items'] items = db['items']
filters = [f for f in items.distinct('Filter2', _active_record_query(_non_library_query())) if f] 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) predefined = get_predefined_filter_values(2)
return sorted(list(set(filters + predefined))) return sorted(list(set(filters + predefined)))
except Exception as e: except Exception as e:
@@ -635,20 +520,13 @@ def get_secondary_filters():
def get_tertiary_filters(): def get_tertiary_filters():
""" """Retrieve all unique tertiary filter values."""
Retrieve all unique tertiary filter values.
Returns:
list: List of all tertiary filter values
"""
try: try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client:
db = client[cfg.MONGODB_DB] db = client[cfg.MONGODB_DB]
items = db['items'] items = db['items']
filters = [f for f in items.distinct('Filter3', _active_record_query(_non_library_query())) if f] 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) predefined = get_predefined_filter_values(3)
return sorted(list(set(filters + predefined))) return sorted(list(set(filters + predefined)))
except Exception as e: except Exception as e:
@@ -659,24 +537,16 @@ def get_tertiary_filters():
def get_item_by_code_4(code_4): def get_item_by_code_4(code_4):
""" """
Retrieve inventory items matching a specific 4-digit code. 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: try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client:
db = client[cfg.MONGODB_DB] db = client[cfg.MONGODB_DB]
items = db['items'] items = db['items']
results = list(items.find(_active_record_query(_non_library_query({"Code_4": code_4})))) results = list(items.find(_active_record_query(_non_library_query({"Code_4": code_4}))))
# Convert ObjectId to string
for item in results: for item in results:
item['_id'] = str(item['_id']) item['_id'] = str(item['_id'])
client.close()
return results return results
except Exception as e: except Exception as e:
print(f"Error retrieving item by code: {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): def unstuck_item(id):
""" """
Remove all borrowing records for a specific item to reset its status. 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: try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client:
db = client[cfg.MONGODB_DB] db = client[cfg.MONGODB_DB]
ausleihungen = db['ausleihungen'] ausleihungen = db['ausleihungen']
result = ausleihungen.update_many(
ausleihungen.update_many(
{'Item': id, 'Status': {'$nin': ['cancelled', 'deleted']}}, {'Item': id, 'Status': {'$nin': ['cancelled', 'deleted']}},
{'$set': { {'$set': {
'Status': 'cancelled', 'Status': 'cancelled',
@@ -709,7 +573,6 @@ def unstuck_item(id):
}} }}
) )
# Also reset the item status
items = db['items'] items = db['items']
items.update_one( items.update_one(
{'_id': ObjectId(id)}, {'_id': ObjectId(id)},
@@ -721,8 +584,6 @@ def unstuck_item(id):
'$unset': {'User': ""} '$unset': {'User': ""}
} }
) )
client.close()
return True return True
except Exception as e: except Exception as e:
print(f"Error unsticking item: {e}") print(f"Error unsticking item: {e}")
@@ -732,128 +593,79 @@ def unstuck_item(id):
def get_predefined_filter_values(filter_num): def get_predefined_filter_values(filter_num):
""" """
Get predefined values for a specific filter. 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] db = client[cfg.MONGODB_DB]
# Use a dedicated collection for filter presets
filter_presets = db['filter_presets'] filter_presets = db['filter_presets']
# Find the document for the specified filter
filter_doc = filter_presets.find_one({'filter_num': filter_num}) filter_doc = filter_presets.find_one({'filter_num': filter_num})
client.close()
if filter_doc and 'values' in filter_doc: if filter_doc and 'values' in filter_doc:
# Sort values alphabetically
return sorted(filter_doc['values']) return sorted(filter_doc['values'])
else: 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_presets.update_one(
{'filter_num': filter_num}, {'filter_num': filter_num},
{'$set': {'values': []}}, {'$set': {'values': []}},
upsert=True upsert=True
) )
client.close()
return [] return []
def add_predefined_filter_value(filter_num, value): def add_predefined_filter_value(filter_num, value):
""" """
Add a new predefined value to a filter. 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] db = client[cfg.MONGODB_DB]
filter_presets = db['filter_presets'] filter_presets = db['filter_presets']
# Check if value already exists
filter_doc = filter_presets.find_one({ filter_doc = filter_presets.find_one({
'filter_num': filter_num, 'filter_num': filter_num,
'values': value 'values': value
}) })
if filter_doc: if filter_doc:
# Value already exists
client.close()
return False return False
# Add the value to the filter
result = filter_presets.update_one( result = filter_presets.update_one(
{'filter_num': filter_num}, {'filter_num': filter_num},
{'$push': {'values': value}}, {'$push': {'values': value}},
upsert=True upsert=True
) )
client.close()
return result.modified_count > 0 or result.upserted_id is not None return result.modified_count > 0 or result.upserted_id is not None
def remove_predefined_filter_value(filter_num, value): def remove_predefined_filter_value(filter_num, value):
""" """
Remove a predefined value from a filter. 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] db = client[cfg.MONGODB_DB]
filter_presets = db['filter_presets'] filter_presets = db['filter_presets']
# Remove the value from the filter
result = filter_presets.update_one( result = filter_presets.update_one(
{'filter_num': filter_num}, {'filter_num': filter_num},
{'$pull': {'values': value}} {'$pull': {'values': value}}
) )
client.close()
return result.modified_count > 0 return result.modified_count > 0
def edit_predefined_filter_value(filter_num, old_value, new_value): def edit_predefined_filter_value(filter_num, old_value, new_value):
""" """
Edit a predefined value from a filter and update all matching items. 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] db = client[cfg.MONGODB_DB]
filter_presets = db['filter_presets'] filter_presets = db['filter_presets']
# Check if the new value already exists
existing = filter_presets.find_one({ existing = filter_presets.find_one({
'filter_num': filter_num, 'filter_num': filter_num,
'values': new_value 'values': new_value
}) })
if existing and old_value != new_value: if existing and old_value != new_value:
client.close()
return False return False
# Update the value in the filter
result = filter_presets.update_one( result = filter_presets.update_one(
{'filter_num': filter_num, 'values': old_value}, {'filter_num': filter_num, 'values': old_value},
{'$set': {'values.$': new_value}} {'$set': {'values.$': new_value}}
@@ -863,41 +675,42 @@ def edit_predefined_filter_value(filter_num, old_value, new_value):
items = db['items'] items = db['items']
filter_field = 'Filter' if filter_num == 1 else f'Filter{filter_num}' filter_field = 'Filter' if filter_num == 1 else f'Filter{filter_num}'
# Also update all items that use this filter
items.update_many( items.update_many(
{filter_field: old_value}, {filter_field: old_value},
{'$set': {filter_field: new_value}} {'$set': {filter_field: new_value}}
) )
client.close()
return result.modified_count > 0 return result.modified_count > 0
def get_filter_names(): def get_filter_names():
"""Get customized filter category 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] db = client[cfg.MONGODB_DB]
names_doc = db.settings.find_one({'setting_type': 'filter_names'}) names_doc = db.settings.find_one({'setting_type': 'filter_names'})
client.close()
if names_doc and 'names' in names_doc: if names_doc and 'names' in names_doc:
return names_doc['names'] return names_doc['names']
return { return {
'1': 'Fach/Kategorie', '1': 'Fach/Kategorie',
'2': 'System/Bereich', '2': 'System/Bereich',
'3': 'Typ/Art' '3': 'Typ/Art'
} }
def set_filter_name(filter_num, name): def set_filter_name(filter_num, name):
"""Set custom name for a filter category.""" """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] db = client[cfg.MONGODB_DB]
names = get_filter_names() names = get_filter_names()
names[str(filter_num)] = name names[str(filter_num)] = name
db.settings.update_one( db.settings.update_one(
{'setting_type': 'filter_names'}, {'setting_type': 'filter_names'},
{'$set': {'names': names}}, {'$set': {'names': names}},
upsert=True upsert=True
) )
client.close()
return True return True
@@ -906,33 +719,25 @@ def set_filter_name(filter_num, name):
def get_predefined_locations(): def get_predefined_locations():
""" """
Get list of all predefined locations/placement options. Get list of all predefined locations/placement options.
Returns:
list: List of predefined location strings
""" """
try: try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client:
db = client[cfg.MONGODB_DB] db = client[cfg.MONGODB_DB]
# Check if settings collection exists, create if not
if 'settings' not in db.list_collection_names(): if 'settings' not in db.list_collection_names():
db.create_collection('settings') db.create_collection('settings')
# Get settings document or create if it doesn't exist
settings_collection = db['settings'] settings_collection = db['settings']
location_settings = settings_collection.find_one({'setting_type': 'predefined_locations'}) location_settings = settings_collection.find_one({'setting_type': 'predefined_locations'})
if not location_settings: if not location_settings:
# Create default settings document if it doesn't exist
settings_collection.insert_one({ settings_collection.insert_one({
'setting_type': 'predefined_locations', 'setting_type': 'predefined_locations',
'locations': [] 'locations': []
}) })
return [] return []
# Return the predefined locations
locations = location_settings.get('locations', []) locations = location_settings.get('locations', [])
client.close()
return sorted(locations) return sorted(locations)
except Exception as e: except Exception as e:
@@ -943,12 +748,6 @@ def get_predefined_locations():
def add_predefined_location(location): def add_predefined_location(location):
""" """
Add a new predefined 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): if not location or not isinstance(location, str):
return False return False
@@ -958,35 +757,27 @@ def add_predefined_location(location):
return False return False
try: try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client:
db = client[cfg.MONGODB_DB] db = client[cfg.MONGODB_DB]
settings_collection = db['settings'] settings_collection = db['settings']
# Check if settings document exists, create if not
location_settings = settings_collection.find_one({'setting_type': 'predefined_locations'}) location_settings = settings_collection.find_one({'setting_type': 'predefined_locations'})
if not location_settings: if not location_settings:
# Create with the new location
settings_collection.insert_one({ settings_collection.insert_one({
'setting_type': 'predefined_locations', 'setting_type': 'predefined_locations',
'locations': [location] 'locations': [location]
}) })
client.close()
return True return True
# Check if location already exists (case-insensitive)
current_locations = location_settings.get('locations', []) current_locations = location_settings.get('locations', [])
if any(loc.lower() == location.lower() for loc in current_locations): if any(loc.lower() == location.lower() for loc in current_locations):
client.close()
return False return False
# Add the new location
settings_collection.update_one( settings_collection.update_one(
{'setting_type': 'predefined_locations'}, {'setting_type': 'predefined_locations'},
{'$push': {'locations': location}} {'$push': {'locations': location}}
) )
client.close()
return True return True
except Exception as e: except Exception as e:
@@ -997,18 +788,12 @@ def add_predefined_location(location):
def remove_predefined_location(location): def remove_predefined_location(location):
""" """
Remove a predefined location. Remove a predefined location.
Args:
location (str): Location to remove
Returns:
bool: True if removed successfully
""" """
if not location: if not location:
return False return False
try: try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client:
db = client[cfg.MONGODB_DB] db = client[cfg.MONGODB_DB]
settings_collection = db['settings'] settings_collection = db['settings']
@@ -1016,8 +801,6 @@ def remove_predefined_location(location):
{'setting_type': 'predefined_locations'}, {'setting_type': 'predefined_locations'},
{'$pull': {'locations': location}} {'$pull': {'locations': location}}
) )
client.close()
return result.modified_count > 0 return result.modified_count > 0
except Exception as e: except Exception as e:
@@ -1028,129 +811,36 @@ def remove_predefined_location(location):
def update_item_next_appointment(item_id, appointment_data): def update_item_next_appointment(item_id, appointment_data):
""" """
Update an item with information about its next scheduled appointment. 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: try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client:
db = client[cfg.MONGODB_DB] db = client[cfg.MONGODB_DB]
items = db['items'] items = db['items']
# Format the appointment data for storage if appointment_data is None:
# Ensure date is a datetime object for MongoDB storage update_query = {
appointment_date = appointment_data['date'] '$unset': {'NextAppointment': ""},
if isinstance(appointment_date, datetime.date) and not isinstance(appointment_date, datetime.datetime): '$set': {'LastUpdated': datetime.datetime.now()}
# 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()
} }
else:
data_to_save = appointment_data.copy()
update_data = { if 'user' in data_to_save and data_to_save['user']:
'NextAppointment': next_appointment, data_to_save['user'] = dp.encrypt_text(data_to_save['user'])
update_query = {
'$set': {
'NextAppointment': data_to_save,
'LastUpdated': datetime.datetime.now() 'LastUpdated': datetime.datetime.now()
} }
}
result = items.update_one( result = items.update_one(
{'_id': ObjectId(item_id)}, {'_id': ObjectId(item_id)},
{'$set': update_data} update_query
) )
client.close()
return result.modified_count > 0 return result.modified_count > 0
except Exception as e: except Exception as e:
print(f"Error updating item next appointment: {e}") print(f"Error updating item next appointment: {e}")
return False 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. Build a deterministic username abbreviation from first and last name.
Uses 3 letters from each name and stores it lowercase. 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) alias = build_name_synonym(first_name, last_name)
return alias.lower() return alias.lower()
@@ -325,7 +318,6 @@ def get_effective_permissions(username):
return build_default_permission_payload('full_access') return build_default_permission_payload('full_access')
preset_key = user.get('PermissionPreset') preset_key = user.get('PermissionPreset')
print(preset_key)
payload = build_default_permission_payload(preset_key) payload = build_default_permission_payload(preset_key)
payload['actions'] = _normalize_bool_map(user.get('ActionPermissions', {}), payload['actions']) payload['actions'] = _normalize_bool_map(user.get('ActionPermissions', {}), payload['actions'])
payload['pages'] = _normalize_bool_map(user.get('PagePermissions', {}), payload['pages']) 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) client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
db = _get_tenant_db(client) db = _get_tenant_db(client)
users = db['users'] 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: 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() client.close()
return result.matched_count > 0 return result.matched_count > 0
@@ -368,7 +360,7 @@ def get_favorites(username):
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
db = _get_tenant_db(client) db = _get_tenant_db(client)
users = db['users'] 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() client.close()
if not user: if not user:
return [] return []
@@ -383,7 +375,7 @@ def add_favorite(username, item_id):
db = _get_tenant_db(client) db = _get_tenant_db(client)
users = db['users'] users = db['users']
users.update_one( users.update_one(
{'$or': [{'Username': username}, {'username': username}]}, {'$or': [{'Username': dp.encrypt_text(username)}, {'username': dp.encrypt_text(username)}]},
{'$addToSet': {'favorites': ObjectId(item_id)}} {'$addToSet': {'favorites': ObjectId(item_id)}}
) )
client.close() client.close()
@@ -398,7 +390,7 @@ def remove_favorite(username, item_id):
db = _get_tenant_db(client) db = _get_tenant_db(client)
users = db['users'] users = db['users']
users.update_one( users.update_one(
{'$or': [{'Username': username}, {'username': username}]}, {'$or': [{'Username': dp.encrypt_text(username)}, {'username': dp.encrypt_text(username)}]},
{'$pull': {'favorites': ObjectId(item_id)}} {'$pull': {'favorites': ObjectId(item_id)}}
) )
client.close() client.close()
@@ -407,16 +399,9 @@ def remove_favorite(username, item_id):
return False return False
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.
Args:
password (str): Password to check
Returns:
bool: True if password is strong enough, False otherwise
""" """
if password is None: if password is None:
return False return False
@@ -437,18 +422,14 @@ def check_password_strength(password):
def hashing(password, salt=None): def hashing(password, salt=None):
""" """
Hasht ein Passwort mit scrypt. 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: if salt is None:
# Neuer Benutzer / Passwortänderung -> Dynamischer Salt
random_salt = os.urandom(16) random_salt = os.urandom(16)
hashed = hashlib.scrypt(password_bytes, salt=random_salt, n=16384, r=8, p=1) hashed = hashlib.scrypt(password_bytes, salt=random_salt, n=16384, r=8, p=1)
return f"v1${random_salt.hex()}${hashed.hex()}" return f"v1${random_salt.hex()}${hashed.hex()}"
else: else:
# Bestehender Benutzer (wird zur Verifizierung aufgerufen)
hashed = hashlib.scrypt(password_bytes, salt=salt, n=16384, r=8, p=1) hashed = hashlib.scrypt(password_bytes, salt=salt, n=16384, r=8, p=1)
return hashed.hex() return hashed.hex()
@@ -456,25 +437,20 @@ def hashing(password, salt=None):
def verify_password(provided_password, stored_password_string): def verify_password(provided_password, stored_password_string):
""" """
Verifiziert ein Passwort gegen einen gespeicherten Hash-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: if not stored_password_string:
return False return False
# Überprüfung für das neue, sichere Format
if stored_password_string.startswith("v1$"): if stored_password_string.startswith("v1$"):
try: try:
_, salt_hex, hash_hex = stored_password_string.split("$") _, salt_hex, hash_hex = stored_password_string.split("$")
salt_bytes = bytes.fromhex(salt_hex) salt_bytes = bytes.fromhex(salt_hex)
# Berechne den Hash des eingegebenen Passworts mit dem extrahierten Salt
calculated_hash = hashing(provided_password, salt=salt_bytes) calculated_hash = hashing(provided_password, salt=salt_bytes)
# Timing-Attack-sicherer Vergleich
return hmac.compare_digest(calculated_hash, hash_hex) return hmac.compare_digest(calculated_hash, hash_hex)
except (ValueError, TypeError): except (ValueError, TypeError):
logger.error("Ungültiges Hash-Format in der Datenbank entdeckt.") logger.error("Ungültiges Hash-Format in der Datenbank entdeckt.")
return False return False
else: else:
# Abwärtskompatibilität: Altes Format mit statischem Salt b'some_salt'
old_static_salt = b'some_salt' old_static_salt = b'some_salt'
calculated_hash = hashing(provided_password, salt=old_static_salt) calculated_hash = hashing(provided_password, salt=old_static_salt)
return hmac.compare_digest(calculated_hash, stored_password_string) return hmac.compare_digest(calculated_hash, stored_password_string)
@@ -496,21 +472,20 @@ def check_nm_pwd(username, password):
db = client[db_name] db = client[db_name]
users = db['users'] 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) user_record = users.find_one(query)
if user_record is None: 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 return None
stored_password = user_record.get('Password') or user_record.get('password') stored_password = user_record.get('Password') or user_record.get('password')
if not verify_password(password, stored_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 return None
# Automatische Migration alter Hashes auf das neue Format if stored_password and not stored_password.startswith("v1$"):
if not stored_password.startswith("v1$"):
users.update_one({'_id': user_record['_id']}, {'$set': {'Password': hashing(password)}}) users.update_one({'_id': user_record['_id']}, {'$set': {'Password': hashing(password)}})
return user_record return user_record
@@ -532,40 +507,35 @@ def add_user(
): ):
""" """
Add a new user to the database. 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) client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
try: try:
db = _get_tenant_db(client) db = _get_tenant_db(client)
users = db['users'] users = db['users']
if not check_password_strength(password):
return False
permission_defaults = build_default_permission_payload(permission_preset) permission_defaults = build_default_permission_payload(permission_preset)
if isinstance(action_permissions, dict): if isinstance(action_permissions, dict):
for key, value in action_permissions.items(): for key, value in action_permissions.items():
permission_defaults['actions'][str(key)] = bool(value) permission_defaults['actions'][str(key)] = bool(value)
if isinstance(page_permissions, dict): if isinstance(page_permissions, dict):
for key, value in page_permissions.items(): for key, value in page_permissions.items():
permission_defaults['pages'][str(key)] = bool(value) permission_defaults['pages'][str(key)] = bool(value)
if permission_preset == "full_access": safe_name = name.strip() if name else ''
can_admin_preset_based = True safe_last_name = last_name.strip() if last_name else ''
else:
can_admin_preset_based = False
user_doc = { user_doc = {
'Username': username, 'Username': dp.encrypt_text(username),
'Password': hashing(password), 'Password': hashing(password),
'Admin': can_admin_preset_based, 'Admin': (permission_preset == "full_access"),
'active_ausleihung': None, 'active_ausleihung': None,
'name': name.strip() if name else '', 'name': dp.encrypt_text(safe_name) if safe_name else '',
'last_name': last_name.strip() if last_name else '', 'last_name': dp.encrypt_text(safe_last_name) if safe_last_name else '',
'IsStudent': bool(is_student), 'IsStudent': bool(is_student),
'PermissionPreset': permission_defaults['preset'], 'PermissionPreset': permission_defaults['preset'],
'ActionPermissions': permission_defaults['actions'], 'ActionPermissions': permission_defaults['actions'],
@@ -602,7 +572,7 @@ def student_card_exists(student_card_id):
def get_user_by_student_card(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) normalized = normalize_student_card_id(student_card_id)
if not normalized: if not normalized:
return None return None
@@ -611,65 +581,44 @@ def get_user_by_student_card(student_card_id):
users = db['student_cards'] users = db['student_cards']
found_user = users.find_one({'SchülerName': normalized}) found_user = users.find_one({'SchülerName': normalized})
client.close() client.close()
# Do not call dp.decrypt_text() here because found_user is a MongoDB dictionary.
return found_user return found_user
def make_admin(username): def make_admin(username):
""" """Grant administrator privileges to a user."""
Grant administrator privileges to a user.
Args:
username (str): Username of the user to promote
Returns:
bool: True if user was promoted successfully
"""
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
db = _get_tenant_db(client) db = _get_tenant_db(client)
users = db['users'] 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: 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() client.close()
return result.matched_count > 0 return result.matched_count > 0
def remove_admin(username): def remove_admin(username):
""" """Remove administrator privileges from a user."""
Remove administrator privileges from a user.
Args:
username (str): Username of the user to demote
Returns:
bool: True if user was demoted successfully
"""
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
db = _get_tenant_db(client) db = _get_tenant_db(client)
users = db['users'] 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: 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() client.close()
return result.matched_count > 0 return result.matched_count > 0
def get_user(username): def get_user(username):
""" """Retrieve a specific user by username."""
Retrieve a specific user by username.
Args:
username (str): Username to search for
Returns:
dict: User document or None if not found
"""
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
try: try:
def find_in_db(database_name): def find_in_db(database_name):
db = client[database_name] db = client[database_name]
users = db['users'] 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() tenant_db, tenant_id = _resolve_request_tenant_db()
if tenant_db: if tenant_db:
user = find_in_db(tenant_db) user = find_in_db(tenant_db)
@@ -682,7 +631,6 @@ def get_user(username):
) )
return None return None
# Fallback to default configured database
user = find_in_db(cfg.MONGODB_DB) user = find_in_db(cfg.MONGODB_DB)
if user: if user:
return user return user
@@ -693,146 +641,88 @@ def get_user(username):
def check_admin(username): def check_admin(username):
""" """Check if a user has administrator privileges."""
Check if a user has administrator privileges.
Args:
username (str): Username to check
Returns:
bool: True if user is an administrator, False otherwise
"""
user = get_user(username) user = get_user(username)
return bool(user and user.get('Admin', False)) return bool(user and user.get('Admin', False))
def update_active_ausleihung(username, id_item, ausleihung): def update_active_ausleihung(username, id_item, ausleihung):
""" """Update a user's active borrowing record."""
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
"""
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
db = _get_tenant_db(client) db = _get_tenant_db(client)
users = db['users'] 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() client.close()
return True return True
def get_active_ausleihung(username): def get_active_ausleihung(username):
""" """Get a user's active borrowing record."""
Get a user's active borrowing record.
Args:
username (str): Username of the user
Returns:
dict: Active borrowing information or None
"""
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
db = _get_tenant_db(client) db = _get_tenant_db(client)
users = db['users'] 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): def has_active_borrowing(username):
""" """Check if a user currently has an active borrowing."""
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
"""
try: try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
db = _get_tenant_db(client) db = _get_tenant_db(client)
users = db['users'] users = db['users']
user = users.find_one({'username': username}) user = users.find_one({'username': dp.encrypt_text(username)}) or users.find_one({'Username': dp.encrypt_text(username)})
if not user: client.close()
user = users.find_one({'Username': username})
if not user: if not user:
client.close()
return False return False
has_active = user.get('active_borrowing', False) return user.get('active_borrowing', False)
client.close()
return has_active
except Exception as e: except Exception as e:
return False return False
def delete_user(username): def delete_user(username):
""" """Delete a user from the database."""
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
"""
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
db = _get_tenant_db(client) db = _get_tenant_db(client)
users = db['users'] 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: if result.deleted_count == 0:
# Try with different field name result = users.delete_one({'Username': dp.encrypt_text(username)})
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
db = _get_tenant_db(client)
users = db['users']
result = users.delete_one({'Username': username})
client.close()
client.close()
return result.deleted_count > 0 return result.deleted_count > 0
def update_active_borrowing(username, item_id, status): def update_active_borrowing(username, item_id, status):
""" """Update a user's active borrowing 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
"""
try: try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
db = _get_tenant_db(client) db = _get_tenant_db(client)
users = db['users'] 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: if result.matched_count == 0:
result = users.update_one( result = users.update_one({'Username': dp.encrypt_text(username)}, update_data)
{'Username': username},
{'$set': {
'active_borrowing': status,
'borrowed_item': item_id if status else None
}}
)
client.close() client.close()
return result.modified_count > 0 return result.modified_count > 0
@@ -841,43 +731,37 @@ def update_active_borrowing(username, item_id, status):
def get_name(username): def get_name(username):
""" """Retrieve the name that is associated with the username."""
Retrieve the name that is assosiated with the username.
Returns:
str: String of name
"""
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
db = _get_tenant_db(client) db = _get_tenant_db(client)
users = db['users'] users = db['users']
user = users.find_one({'Username': username})
name = user.get("name") user = users.find_one({'Username': dp.encrypt_text(username)}) or users.find_one({'username': dp.encrypt_text(username)})
return name client.close()
if not user or not user.get("name"):
return ""
return dp.decrypt_text(user.get("name"))
def get_last_name(username): def get_last_name(username):
""" """Retrieve the last_name that is associated with the username."""
Retrieve the last_name that is assosiated with the username.
Returns:
str: String of last_name
"""
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
db = _get_tenant_db(client) db = _get_tenant_db(client)
users = db['users'] users = db['users']
user = users.find_one({'Username': username})
name = user.get("last_name") user = users.find_one({'Username': dp.encrypt_text(username)}) or users.find_one({'username': dp.encrypt_text(username)})
return name client.close()
if not user or not user.get("last_name"):
return ""
return dp.decrypt_text(user.get("last_name"))
def get_all_users(): def get_all_users():
""" """Retrieve all users from the database."""
Retrieve all users from the database.
Administrative function for user management.
Returns:
list: List of all user documents
"""
try: try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
db = _get_tenant_db(client) db = _get_tenant_db(client)
@@ -888,17 +772,9 @@ def get_all_users():
except Exception as e: except Exception as e:
return [] return []
def update_password(username, new_password): def update_password(username, new_password):
""" """Update a user's password with a new one."""
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
"""
try: try:
if not check_password_strength(new_password): if not check_password_strength(new_password):
return False return False
@@ -907,12 +783,15 @@ def update_password(username, new_password):
db = _get_tenant_db(client) db = _get_tenant_db(client)
users = db['users'] users = db['users']
# Hash the new password
hashed_password = hashing(new_password) hashed_password = hashing(new_password)
# Update the user's password
result = users.update_one( 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}} {'$set': {'Password': hashed_password}}
) )
@@ -922,27 +801,25 @@ def update_password(username, new_password):
print(f"Error updating password: {e}") print(f"Error updating password: {e}")
return False return False
def update_user_name(username, name, last_name): def update_user_name(username, name, last_name):
""" """Update a user's name and 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
"""
try: try:
name_alias = build_name_synonym(name, last_name)
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
db = _get_tenant_db(client) db = _get_tenant_db(client)
users = db['users'] 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( result = users.update_one(
{'Username': username}, {'Username': dp.encrypt_text(username)},
{'$set': {'name': name_alias, 'last_name': ''}} {'$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() client.close()