first commit
This commit is contained in:
BIN
server/.DS_Store
vendored
Normal file
BIN
server/.DS_Store
vendored
Normal file
Binary file not shown.
58
server/app.py
Normal file
58
server/app.py
Normal file
@@ -0,0 +1,58 @@
|
||||
import os
|
||||
|
||||
from flask import Flask
|
||||
from flask_jwt_extended import JWTManager
|
||||
from flask_cors import CORS
|
||||
from routes.auth import auth_bp
|
||||
from routes.documents import documents_bp
|
||||
from routes.users import users_bp
|
||||
from routes.payments import payments_bp
|
||||
from routes.subscriptions import subscriptions_bp
|
||||
|
||||
def create_app(test_config=None):
|
||||
# create and configure the app
|
||||
app = Flask(__name__, instance_relative_config=True)
|
||||
|
||||
app.config["JWT_SECRET_KEY"] = os.environ.get("JWT_SECRET_KEY", "your-jwt-secret")
|
||||
app.config["JWT_TOKEN_LOCATION"] = ["headers", "query_string"]
|
||||
app.config["JWT_QUERY_STRING_NAME"] = "token"
|
||||
jwt = JWTManager(app)
|
||||
|
||||
CORS(
|
||||
app,
|
||||
resources={r"/*": {"origins": [os.getenv("WEB_ORIGIN", "*")]}},
|
||||
allow_headers=["Authorization", "Content-Type"],
|
||||
expose_headers=["Content-Type"],
|
||||
)
|
||||
|
||||
app.config.from_mapping(
|
||||
SECRET_KEY='dev',
|
||||
DATABASE=os.path.join(app.instance_path, 'instance/app_database.sqlite'),
|
||||
)
|
||||
|
||||
if test_config is None:
|
||||
# load the instance config, if it exists, when not testing
|
||||
app.config.from_pyfile('config.py', silent=True)
|
||||
else:
|
||||
# load the test config if passed in
|
||||
app.config.from_mapping(test_config)
|
||||
|
||||
# ensure the instance folder exists
|
||||
os.makedirs(app.instance_path, exist_ok=True)
|
||||
|
||||
# a simple page that says hello
|
||||
@app.route('/hello')
|
||||
def hello():
|
||||
return 'Hello, World!'
|
||||
|
||||
app.register_blueprint(auth_bp, url_prefix="/auth")
|
||||
app.register_blueprint(documents_bp, url_prefix="/documents")
|
||||
app.register_blueprint(users_bp, url_prefix="/users")
|
||||
app.register_blueprint(payments_bp, url_prefix="/payments")
|
||||
app.register_blueprint(subscriptions_bp, url_prefix="/subscriptions")
|
||||
|
||||
return app
|
||||
|
||||
if __name__=="__main__":
|
||||
app = create_app()
|
||||
app.run()
|
||||
BIN
server/assets/Manual.pdf
Normal file
BIN
server/assets/Manual.pdf
Normal file
Binary file not shown.
BIN
server/instance/app_database.db
Normal file
BIN
server/instance/app_database.db
Normal file
Binary file not shown.
BIN
server/models/.DS_Store
vendored
Normal file
BIN
server/models/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
server/models/__pycache__/audit.cpython-313.pyc
Normal file
BIN
server/models/__pycache__/audit.cpython-313.pyc
Normal file
Binary file not shown.
BIN
server/models/__pycache__/users.cpython-313.pyc
Normal file
BIN
server/models/__pycache__/users.cpython-313.pyc
Normal file
Binary file not shown.
108
server/models/audit.py
Normal file
108
server/models/audit.py
Normal file
@@ -0,0 +1,108 @@
|
||||
import sqlite3
|
||||
from dataclasses import dataclass
|
||||
import hashlib
|
||||
from typing import Optional
|
||||
|
||||
@dataclass
|
||||
class AuditModel:
|
||||
id: Optional[int] = None
|
||||
user_id: Optional[int] = None
|
||||
action: Optional[str] = None
|
||||
endpoint: Optional[str] = None
|
||||
created_at: Optional[str] = None
|
||||
status: Optional[str] = None
|
||||
|
||||
|
||||
class Audit:
|
||||
def __init__(self, db_path="instance/app_database.db"):
|
||||
self.db_path = db_path
|
||||
self._create_audit_table()
|
||||
|
||||
def _create_audit_table(self):
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS audit (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
action TEXT,
|
||||
endpoint TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
status TEXT
|
||||
);
|
||||
"""
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
def new_entry(self, entry:AuditModel):
|
||||
"""Create a new entry."""
|
||||
try:
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
"""
|
||||
INSERT INTO audit (user_id, action, endpoint, status)
|
||||
VALUES (?, ?, ?, ?)
|
||||
""",
|
||||
(entry.user_id, entry.action, entry.endpoint, entry.status),
|
||||
)
|
||||
conn.commit()
|
||||
return cursor.lastrowid
|
||||
except sqlite3.IntegrityError:
|
||||
return None
|
||||
|
||||
def get_all_entries(self):
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT * FROM audit")
|
||||
rows = cursor.fetchall()
|
||||
|
||||
return [
|
||||
AuditModel(
|
||||
id = row[0],
|
||||
user_id = row[1],
|
||||
action = row[2],
|
||||
endpoint = row[3],
|
||||
created_at = row[4],
|
||||
status = row[5]
|
||||
)
|
||||
for row in rows
|
||||
]
|
||||
|
||||
def get_entries_by_user_id(self, user_id):
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT * FROM audit WHERE user_id = ?", (user_id, ))
|
||||
rows = cursor.fetchall()
|
||||
|
||||
return [
|
||||
AuditModel(
|
||||
id = row[0],
|
||||
user_id = row[1],
|
||||
action = row[2],
|
||||
endpoint = row[3],
|
||||
created_at = row[4],
|
||||
status = row[5]
|
||||
)
|
||||
for row in rows
|
||||
]
|
||||
|
||||
def delete_entries_older_than(self, date_string):
|
||||
"""
|
||||
Deletes logs older than the provided date.
|
||||
Expected date_string format: 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'
|
||||
"""
|
||||
try:
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
# SQLite allows direct string comparison for ISO 8601 dates
|
||||
cursor.execute(
|
||||
"DELETE FROM audit WHERE created_at < ?",
|
||||
(date_string,)
|
||||
)
|
||||
conn.commit()
|
||||
return cursor.rowcount # Returns the number of deleted rows
|
||||
except sqlite3.Error as e:
|
||||
print(f"An error occurred: {e}")
|
||||
return 0
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
132
server/models/documents/documents_category.py
Normal file
132
server/models/documents/documents_category.py
Normal file
@@ -0,0 +1,132 @@
|
||||
import sqlite3
|
||||
from dataclasses import dataclass
|
||||
import hashlib
|
||||
from typing import Optional
|
||||
|
||||
@dataclass
|
||||
class DocumentsCategoryModel:
|
||||
id: Optional[int] = None
|
||||
user_id: Optional[int] = None
|
||||
name: Optional[str] = None
|
||||
created_at: Optional[str] = None
|
||||
access: Optional[str] = None
|
||||
|
||||
class DocumentsCategory:
|
||||
def __init__(self, db_path="instance/app_database.db"):
|
||||
self.db_path = db_path
|
||||
self._create_audit_table()
|
||||
|
||||
def _create_audit_table(self):
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS documents_category (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
name TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
access TEXT
|
||||
);
|
||||
"""
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
def new_entry(self, entry:DocumentsCategoryModel):
|
||||
"""Create a new entry."""
|
||||
try:
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
"""
|
||||
INSERT INTO documents_category (user_id, name, access)
|
||||
VALUES (?, ?, ?)
|
||||
""",
|
||||
(entry.user_id, entry.name, entry.access)
|
||||
)
|
||||
conn.commit()
|
||||
return cursor.lastrowid
|
||||
except sqlite3.IntegrityError:
|
||||
return None
|
||||
|
||||
def get_all_entries(self):
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT * FROM documents_category")
|
||||
rows = cursor.fetchall()
|
||||
|
||||
return [
|
||||
DocumentsCategoryModel(
|
||||
id = row[0],
|
||||
user_id = row[1],
|
||||
name = row[2],
|
||||
created_at = row[3],
|
||||
access = row[4]
|
||||
)
|
||||
for row in rows
|
||||
]
|
||||
|
||||
def get_entries_by_user_id(self, user_id):
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT * FROM documents_category WHERE user_id = ?", (user_id, ))
|
||||
rows = cursor.fetchall()
|
||||
|
||||
return [
|
||||
DocumentsCategoryModel(
|
||||
id = row[0],
|
||||
user_id = row[1],
|
||||
name = row[2],
|
||||
created_at = row[3],
|
||||
access = row[4]
|
||||
)
|
||||
for row in rows
|
||||
]
|
||||
|
||||
def get_entry_by_id(self, id):
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT * FROM documents_category WHERE id = ?", (id, ))
|
||||
row = cursor.fetchone()
|
||||
if row:
|
||||
return DocumentsCategoryModel(
|
||||
id = row[0],
|
||||
user_id = row[1],
|
||||
name = row[2],
|
||||
created_at = row[3],
|
||||
access = row[4]
|
||||
)
|
||||
return None
|
||||
|
||||
def update_entry(self, id, name, access):
|
||||
try:
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
"""
|
||||
UPDATE documents_category
|
||||
SET name = ?, access = ?
|
||||
WHERE id = ?
|
||||
""",
|
||||
(name, access, id)
|
||||
)
|
||||
conn.commit()
|
||||
return cursor.rowcount
|
||||
except sqlite3.Error as e:
|
||||
print(f"An error occurred: {e}")
|
||||
return 0
|
||||
|
||||
def delete_entry(self, id):
|
||||
try:
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
# SQLite allows direct string comparison for ISO 8601 dates
|
||||
cursor.execute(
|
||||
"DELETE FROM documents_category WHERE id = ? ",
|
||||
(id,)
|
||||
)
|
||||
conn.commit()
|
||||
return cursor.rowcount # Returns the number of deleted rows
|
||||
except sqlite3.Error as e:
|
||||
print(f"An error occurred: {e}")
|
||||
return 0
|
||||
303
server/models/documents/documents_custom.py
Normal file
303
server/models/documents/documents_custom.py
Normal file
@@ -0,0 +1,303 @@
|
||||
import sqlite3
|
||||
from dataclasses import dataclass
|
||||
import hashlib
|
||||
from typing import Optional
|
||||
|
||||
@dataclass
|
||||
class DocumentsCustomModel:
|
||||
id: Optional[int] = None
|
||||
user_id: Optional[int] = None
|
||||
name: Optional[str] = None
|
||||
path: Optional[str] = None
|
||||
created_at: Optional[str] = None
|
||||
access: Optional[str] = None
|
||||
|
||||
|
||||
class DocumentsCustom:
|
||||
def __init__(self, db_path="instance/app_database.db"):
|
||||
self.db_path = db_path
|
||||
self._create_audit_table()
|
||||
|
||||
def _create_audit_table(self):
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS documents_custom (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
name TEXT,
|
||||
path TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
access TEXT
|
||||
);
|
||||
"""
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
def new_entry(self, entry:DocumentsCustomModel):
|
||||
"""Create a new entry."""
|
||||
try:
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
"""
|
||||
INSERT INTO documents_custom (user_id, name, path, access)
|
||||
VALUES (?, ?, ?, ?)
|
||||
""",
|
||||
(entry.user_id, entry.name, entry.path, entry.access),
|
||||
)
|
||||
conn.commit()
|
||||
return cursor.lastrowid
|
||||
except sqlite3.IntegrityError:
|
||||
return None
|
||||
|
||||
def get_all_entries(self):
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT * FROM documents_custom")
|
||||
rows = cursor.fetchall()
|
||||
|
||||
return [
|
||||
DocumentsCustomModel(
|
||||
id = row[0],
|
||||
user_id = row[1],
|
||||
name = row[2],
|
||||
path = row[3],
|
||||
created_at = row[4],
|
||||
access = row[5]
|
||||
)
|
||||
for row in rows
|
||||
]
|
||||
|
||||
def get_entries_by_user_id(self, user_id):
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT * FROM documents_custom WHERE user_id = ?", (user_id, ))
|
||||
rows = cursor.fetchall()
|
||||
|
||||
return [
|
||||
DocumentsCustomModel(
|
||||
id = row[0],
|
||||
user_id = row[1],
|
||||
name = row[2],
|
||||
path = row[3],
|
||||
created_at = row[4],
|
||||
access = row[5]
|
||||
)
|
||||
for row in rows
|
||||
]
|
||||
|
||||
def get_entry_by_id(self, id):
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT * FROM documents_custom WHERE id = ?", (id, ))
|
||||
row = cursor.fetchone()
|
||||
if row:
|
||||
return DocumentsCustomModel(
|
||||
id = row[0],
|
||||
user_id = row[1],
|
||||
name = row[2],
|
||||
path = row[3],
|
||||
created_at = row[4],
|
||||
access = row[5]
|
||||
)
|
||||
return None
|
||||
|
||||
def delete_entry(self, id):
|
||||
try:
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
# SQLite allows direct string comparison for ISO 8601 dates
|
||||
cursor.execute(
|
||||
"DELETE FROM documents_custom WHERE id = ? ",
|
||||
(id,)
|
||||
)
|
||||
conn.commit()
|
||||
return cursor.rowcount # Returns the number of deleted rows
|
||||
except sqlite3.Error as e:
|
||||
print(f"An error occurred: {e}")
|
||||
return 0
|
||||
|
||||
@dataclass
|
||||
class CustomDocumentRequestModel:
|
||||
id: Optional[int] = None
|
||||
client_id: Optional[int] = None
|
||||
request_text: Optional[str] = None
|
||||
status: Optional[str] = "new"
|
||||
price: Optional[float] = 0.0
|
||||
expert_id: Optional[int] = None
|
||||
document_id: Optional[int] = None
|
||||
created_at: Optional[str] = None
|
||||
|
||||
class CustomDocumentRequests:
|
||||
def __init__(self, db_path="instance/app_database.db"):
|
||||
self.db_path = db_path
|
||||
self._create_table()
|
||||
|
||||
def _create_table(self):
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS custom_document_requests (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
client_id INTEGER NOT NULL,
|
||||
request_text TEXT,
|
||||
status TEXT DEFAULT 'new',
|
||||
price REAL DEFAULT 0.0,
|
||||
expert_id INTEGER,
|
||||
document_id INTEGER,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
"""
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
def new_entry(self, entry: CustomDocumentRequestModel):
|
||||
try:
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
"""
|
||||
INSERT INTO custom_document_requests (client_id, request_text, status, price, expert_id, document_id)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
(entry.client_id, entry.request_text, entry.status, entry.price, entry.expert_id, entry.document_id),
|
||||
)
|
||||
conn.commit()
|
||||
return cursor.lastrowid
|
||||
except sqlite3.Error as e:
|
||||
print(f"Database error: {e}")
|
||||
return None
|
||||
|
||||
def get_all_entries(self):
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT * FROM custom_document_requests")
|
||||
rows = cursor.fetchall()
|
||||
return [
|
||||
CustomDocumentRequestModel(
|
||||
id=row[0],
|
||||
client_id=row[1],
|
||||
request_text=row[2],
|
||||
status=row[3],
|
||||
price=row[4],
|
||||
expert_id=row[5],
|
||||
document_id=row[6],
|
||||
created_at=row[7]
|
||||
) for row in rows]
|
||||
|
||||
def get_entry_by_id(self, id):
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT * FROM custom_document_requests WHERE id = ?", (id,))
|
||||
row = cursor.fetchone()
|
||||
if row:
|
||||
return CustomDocumentRequestModel(
|
||||
id=row[0],
|
||||
client_id=row[1],
|
||||
request_text=row[2],
|
||||
status=row[3],
|
||||
price=row[4],
|
||||
expert_id=row[5],
|
||||
document_id=row[6],
|
||||
created_at=row[7]
|
||||
)
|
||||
return None
|
||||
|
||||
def get_entries_by_client_id(self, client_id):
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT * FROM custom_document_requests WHERE client_id = ?", (client_id,))
|
||||
rows = cursor.fetchall()
|
||||
return [
|
||||
CustomDocumentRequestModel(
|
||||
id=row[0],
|
||||
client_id=row[1],
|
||||
request_text=row[2],
|
||||
status=row[3],
|
||||
price=row[4],
|
||||
expert_id=row[5],
|
||||
document_id=row[6],
|
||||
created_at=row[7]
|
||||
) for row in rows]
|
||||
|
||||
def get_entries_by_expert_id(self, expert_id):
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT * FROM custom_document_requests WHERE expert_id = ? ORDER BY id DESC", (expert_id,))
|
||||
rows = cursor.fetchall()
|
||||
return [
|
||||
CustomDocumentRequestModel(
|
||||
id=row[0],
|
||||
client_id=row[1],
|
||||
request_text=row[2],
|
||||
status=row[3],
|
||||
price=row[4],
|
||||
expert_id=row[5],
|
||||
document_id=row[6],
|
||||
created_at=row[7]
|
||||
) for row in rows]
|
||||
|
||||
def update_entry(self, id, status=None, price=None, expert_id=None, document_id=None, request_text=None):
|
||||
if status is None and price is None and expert_id is None and document_id is None and request_text is None:
|
||||
return False
|
||||
|
||||
# Fetch existing entry to preserve unchanged values if not provided
|
||||
existing_entry = self.get_entry_by_id(id)
|
||||
if not existing_entry:
|
||||
return False
|
||||
|
||||
# Use existing values if new ones are not provided
|
||||
status = status if status is not None else existing_entry.status
|
||||
price = price if price is not None else existing_entry.price
|
||||
expert_id = expert_id if expert_id is not None else existing_entry.expert_id
|
||||
document_id = document_id if document_id is not None else existing_entry.document_id
|
||||
request_text = request_text if request_text is not None else existing_entry.request_text
|
||||
|
||||
fields = []
|
||||
params = []
|
||||
|
||||
if request_text is not None:
|
||||
fields.append("request_text = ?")
|
||||
params.append(request_text)
|
||||
if status is not None:
|
||||
fields.append("status = ?")
|
||||
params.append(status)
|
||||
if price is not None:
|
||||
fields.append("price = ?")
|
||||
params.append(price)
|
||||
if expert_id is not None:
|
||||
fields.append("expert_id = ?")
|
||||
params.append(expert_id)
|
||||
if document_id is not None:
|
||||
fields.append("document_id = ?")
|
||||
params.append(document_id)
|
||||
|
||||
params.append(id)
|
||||
query = f"UPDATE custom_document_requests SET {', '.join(fields)} WHERE id = ?"
|
||||
|
||||
try:
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(query, tuple(params))
|
||||
conn.commit()
|
||||
return cursor.rowcount > 0
|
||||
except sqlite3.Error as e:
|
||||
print(f"An error occurred during update: {e}")
|
||||
return False
|
||||
|
||||
def delete_entry(self, id):
|
||||
try:
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
"DELETE FROM custom_document_requests WHERE id = ?",
|
||||
(id,)
|
||||
)
|
||||
conn.commit()
|
||||
return cursor.rowcount
|
||||
except sqlite3.Error as e:
|
||||
print(f"An error occurred during delete: {e}")
|
||||
return 0
|
||||
143
server/models/documents/documents_standard.py
Normal file
143
server/models/documents/documents_standard.py
Normal file
@@ -0,0 +1,143 @@
|
||||
import sqlite3
|
||||
from dataclasses import dataclass
|
||||
import hashlib
|
||||
from typing import Optional
|
||||
|
||||
@dataclass
|
||||
class DocumentsStandardModel:
|
||||
id: Optional[int] = None
|
||||
category_id: Optional[int] = None
|
||||
user_id: Optional[int] = None
|
||||
name: Optional[str] = None
|
||||
path: Optional[str] = None
|
||||
created_at: Optional[str] = None
|
||||
access: Optional[str] = None
|
||||
|
||||
|
||||
class DocumentsStandard:
|
||||
def __init__(self, db_path="instance/app_database.db"):
|
||||
self.db_path = db_path
|
||||
self._create_audit_table()
|
||||
|
||||
def _create_audit_table(self):
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS documents_standard (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
category_id INTEGER,
|
||||
user_id INTEGER NOT NULL,
|
||||
name TEXT,
|
||||
path TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
access TEXT
|
||||
);
|
||||
"""
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
def new_entry(self, entry:DocumentsStandardModel):
|
||||
"""Create a new entry."""
|
||||
try:
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
"""
|
||||
INSERT INTO documents_standard (category_id, user_id, name, path, access)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
""",
|
||||
(entry.category_id, entry.user_id, entry.name, entry.path, entry.access),
|
||||
)
|
||||
conn.commit()
|
||||
return cursor.lastrowid
|
||||
except sqlite3.IntegrityError:
|
||||
return None
|
||||
|
||||
def get_all_entries(self):
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT * FROM documents_standard")
|
||||
rows = cursor.fetchall()
|
||||
|
||||
return [
|
||||
DocumentsStandardModel(
|
||||
id = row[0],
|
||||
category_id = row[1],
|
||||
user_id = row[2],
|
||||
name = row[3],
|
||||
path = row[4],
|
||||
created_at = row[5],
|
||||
access = row[6]
|
||||
)
|
||||
for row in rows
|
||||
]
|
||||
|
||||
def get_entries_by_user_id(self, user_id):
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT * FROM documents_standard WHERE user_id = ?", (user_id, ))
|
||||
rows = cursor.fetchall()
|
||||
|
||||
return [
|
||||
DocumentsStandardModel(
|
||||
id = row[0],
|
||||
category_id = row[1],
|
||||
user_id = row[2],
|
||||
name = row[3],
|
||||
path = row[4],
|
||||
created_at = row[5],
|
||||
access = row[6]
|
||||
)
|
||||
for row in rows
|
||||
]
|
||||
|
||||
def get_entries_by_category(self, category_id):
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT * FROM documents_standard WHERE category_id = ?", (category_id, ))
|
||||
rows = cursor.fetchall()
|
||||
return [
|
||||
DocumentsStandardModel(
|
||||
id = row[0],
|
||||
category_id = row[1],
|
||||
user_id = row[2],
|
||||
name = row[3],
|
||||
path = row[4],
|
||||
created_at = row[5],
|
||||
access = row[6]
|
||||
)
|
||||
for row in rows
|
||||
]
|
||||
|
||||
def get_entry_by_id(self, id):
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT * FROM documents_standard WHERE id = ?", (id, ))
|
||||
row = cursor.fetchone()
|
||||
if row:
|
||||
return DocumentsStandardModel(
|
||||
id = row[0],
|
||||
category_id = row[1],
|
||||
user_id = row[2],
|
||||
name = row[3],
|
||||
path = row[4],
|
||||
created_at = row[5],
|
||||
access = row[6]
|
||||
)
|
||||
return None
|
||||
|
||||
def delete_entry(self, id):
|
||||
try:
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
# SQLite allows direct string comparison for ISO 8601 dates
|
||||
cursor.execute(
|
||||
"DELETE FROM documents_standard WHERE id = ? ",
|
||||
(id,)
|
||||
)
|
||||
conn.commit()
|
||||
return cursor.rowcount # Returns the number of deleted rows
|
||||
except sqlite3.Error as e:
|
||||
print(f"An error occurred: {e}")
|
||||
return 0
|
||||
BIN
server/models/payments/__pycache__/payments.cpython-313.pyc
Normal file
BIN
server/models/payments/__pycache__/payments.cpython-313.pyc
Normal file
Binary file not shown.
BIN
server/models/payments/__pycache__/subscriptions.cpython-313.pyc
Normal file
BIN
server/models/payments/__pycache__/subscriptions.cpython-313.pyc
Normal file
Binary file not shown.
Binary file not shown.
124
server/models/payments/payments.py
Normal file
124
server/models/payments/payments.py
Normal file
@@ -0,0 +1,124 @@
|
||||
import sqlite3
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
@dataclass
|
||||
class PaymentsModel:
|
||||
id: Optional[int] = None
|
||||
user_id: Optional[int] = None
|
||||
name: Optional[str] = None
|
||||
amount: Optional[float] = None
|
||||
type: Optional[str] = None
|
||||
created_at: Optional[str] = None
|
||||
|
||||
class Payments:
|
||||
def __init__(self, db_path="instance/app_database.db"):
|
||||
self.db_path = db_path
|
||||
self._create_payment_table()
|
||||
|
||||
def _create_payment_table(self):
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS payments (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
name TEXT,
|
||||
amount REAL,
|
||||
type TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
"""
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
def add_payment(self, payment: PaymentsModel):
|
||||
"""Adds a new payment entry to the database."""
|
||||
try:
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
"""
|
||||
INSERT INTO subscriptions_and_payments (user_id, name, amount, type)
|
||||
VALUES (?, ?, ?, ?)
|
||||
""",
|
||||
(payment.user_id, payment.name, payment.amount, payment.type),
|
||||
)
|
||||
conn.commit()
|
||||
return cursor.lastrowid
|
||||
except sqlite3.IntegrityError:
|
||||
return None
|
||||
|
||||
def get_payment(self, payment_id: int) -> PaymentsModel | None:
|
||||
"""Retrieves a single payment entry by its ID."""
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT * FROM subscriptions_and_payments WHERE id = ?", (payment_id,))
|
||||
row = cursor.fetchone()
|
||||
|
||||
if not row:
|
||||
return None
|
||||
|
||||
return PaymentsModel(
|
||||
id=row[0],
|
||||
user_id=row[1],
|
||||
name=row[2],
|
||||
amount=row[3],
|
||||
type=row[4],
|
||||
created_at=row[5]
|
||||
)
|
||||
|
||||
def get_all_payments(self):
|
||||
"""Retrieves all payment entries from the database."""
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT * FROM subscriptions_and_payments")
|
||||
rows = cursor.fetchall()
|
||||
|
||||
return [
|
||||
PaymentsModel(
|
||||
id=row[0],
|
||||
user_id=row[1],
|
||||
name=row[2],
|
||||
amount=row[3],
|
||||
type=row[4],
|
||||
created_at=row[5]
|
||||
)
|
||||
for row in rows
|
||||
]
|
||||
|
||||
def update_payment(self, payment_id: int, name: Optional[str] = None, amount: Optional[float] = None, type: Optional[str] = None):
|
||||
"""Updates an existing payment entry."""
|
||||
fields = []
|
||||
params = []
|
||||
|
||||
if name is not None:
|
||||
fields.append("name = ?")
|
||||
params.append(name)
|
||||
if amount is not None:
|
||||
fields.append("amount = ?")
|
||||
params.append(amount)
|
||||
if type is not None:
|
||||
fields.append("type = ?")
|
||||
params.append(type)
|
||||
|
||||
if not fields:
|
||||
return False # No fields to update
|
||||
|
||||
params.append(payment_id)
|
||||
query = f"UPDATE subscriptions_and_payments SET {', '.join(fields)} WHERE id = ?"
|
||||
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(query, tuple(params))
|
||||
conn.commit()
|
||||
return cursor.rowcount > 0
|
||||
|
||||
def delete_payment(self, payment_id: int):
|
||||
"""Deletes a payment entry by its ID."""
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("DELETE FROM subscriptions_and_payments WHERE id = ?", (payment_id,))
|
||||
conn.commit()
|
||||
return cursor.rowcount > 0
|
||||
112
server/models/payments/subscriptions.py
Normal file
112
server/models/payments/subscriptions.py
Normal file
@@ -0,0 +1,112 @@
|
||||
import sqlite3
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
@dataclass
|
||||
class SubscriptionsModel:
|
||||
id: Optional[int] = None
|
||||
user_id: Optional[int] = None
|
||||
name: Optional[str] = None # Numele abonamentului (ex: "Abonament Lunar", "Abonament Anual")
|
||||
pay_and_subs_id: Optional[int] = None # ID-ul plății asociate din tabela subscriptions_and_payments
|
||||
mounts: Optional[int] = None
|
||||
created_at: Optional[str] = None
|
||||
|
||||
class Subscriptions:
|
||||
def __init__(self, db_path="instance/app_database.db"):
|
||||
self.db_path = db_path
|
||||
self._create_subscription_table()
|
||||
|
||||
def _create_subscription_table(self):
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS subscriptions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
name TEXT,
|
||||
pay_and_subs_id INTEGER,
|
||||
mounts INTEGER,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id),
|
||||
FOREIGN KEY (pay_and_subs_id) REFERENCES subscriptions_and_payments(id)
|
||||
);
|
||||
"""
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
def add_subscription(self, subscription: SubscriptionsModel):
|
||||
"""Adds a new subscription entry to the database."""
|
||||
try:
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
"""
|
||||
INSERT INTO subscriptions (user_id, name, pay_and_subs_id, mounts)
|
||||
VALUES (?, ?, ?, ?)
|
||||
""",
|
||||
(subscription.user_id, subscription.name, subscription.pay_and_subs_id, subscription.mounts),
|
||||
)
|
||||
conn.commit()
|
||||
return cursor.lastrowid
|
||||
except sqlite3.Error:
|
||||
return None
|
||||
|
||||
def get_subscription(self, subscription_id: int) -> Optional[SubscriptionsModel]:
|
||||
"""Retrieves a single subscription entry by its ID."""
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT * FROM subscriptions WHERE id = ?", (subscription_id,))
|
||||
row = cursor.fetchone()
|
||||
if row:
|
||||
return SubscriptionsModel(*row)
|
||||
return None
|
||||
|
||||
def get_all_subscriptions(self):
|
||||
"""Retrieves all subscription entries from the database."""
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT * FROM subscriptions")
|
||||
rows = cursor.fetchall()
|
||||
return [SubscriptionsModel(*row) for row in rows]
|
||||
|
||||
def get_subscriptions_by_user_id(self, user_id: int):
|
||||
"""Retrieves all subscriptions for a specific user."""
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT * FROM subscriptions WHERE user_id = ?", (user_id,))
|
||||
rows = cursor.fetchall()
|
||||
return [SubscriptionsModel(*row) for row in rows]
|
||||
|
||||
def update_subscription(self, subscription_id: int, name: Optional[str] = None, pay_and_subs_id: Optional[int] = None, mounts: Optional[int] = None):
|
||||
"""Updates an existing subscription entry."""
|
||||
fields = []
|
||||
params = []
|
||||
if name is not None:
|
||||
fields.append("name = ?")
|
||||
params.append(name)
|
||||
if pay_and_subs_id is not None:
|
||||
fields.append("pay_and_subs_id = ?")
|
||||
params.append(pay_and_subs_id)
|
||||
if mounts is not None:
|
||||
fields.append("mounts = ?")
|
||||
params.append(mounts)
|
||||
|
||||
if not fields:
|
||||
return False
|
||||
|
||||
params.append(subscription_id)
|
||||
query = f"UPDATE subscriptions SET {', '.join(fields)} WHERE id = ?"
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(query, tuple(params))
|
||||
conn.commit()
|
||||
return cursor.rowcount > 0
|
||||
|
||||
def delete_subscription(self, subscription_id: int):
|
||||
"""Deletes a subscription entry by its ID."""
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("DELETE FROM subscriptions WHERE id = ?", (subscription_id,))
|
||||
conn.commit()
|
||||
return cursor.rowcount > 0
|
||||
323
server/models/users.py
Normal file
323
server/models/users.py
Normal file
@@ -0,0 +1,323 @@
|
||||
import sqlite3
|
||||
from dataclasses import dataclass
|
||||
import hashlib
|
||||
from typing import Optional
|
||||
|
||||
@dataclass
|
||||
class UserModel:
|
||||
id: Optional[int] = None
|
||||
workspace_id: Optional[int] = None
|
||||
first_name: Optional[str] = None
|
||||
last_name: Optional[str] = None
|
||||
email: Optional[str] = None
|
||||
password: Optional[str] = None
|
||||
address: Optional[str] = None
|
||||
profession: Optional[str] = None
|
||||
role: Optional[str] = None
|
||||
status: Optional[str] = None
|
||||
profile_pic: Optional[str] = None
|
||||
created_at: Optional[str] = None
|
||||
otp_code: Optional[str] = None
|
||||
otp_expiration: Optional[str] = None
|
||||
active: Optional[int] = None
|
||||
|
||||
class Users:
|
||||
def __init__(self, db_path="instance/app_database.db"):
|
||||
self.db_path = db_path
|
||||
self._create_users_table()
|
||||
|
||||
def _create_users_table(self):
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
workspace_id INTEGER NOT NULL,
|
||||
first_name TEXT,
|
||||
last_name TEXT,
|
||||
email TEXT UNIQUE,
|
||||
password TEXT,
|
||||
address TEXT,
|
||||
profession TEXT,
|
||||
role TEXT DEFAULT 'user',
|
||||
status TEXT,
|
||||
profile_pic TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
otp_code TEXT,
|
||||
otp_expiration TIMESTAMPTZ,
|
||||
active INTEGER DEFAULT 1
|
||||
);
|
||||
"""
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
def update_user_otp(self, user_id, otp_code, expiration):
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
f"""
|
||||
UPDATE users
|
||||
SET otp_code = ?, otp_expiration = ?
|
||||
WHERE id = ?
|
||||
""",
|
||||
(otp_code, expiration.isoformat(), user_id)
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
def clear_user_otp(self, user_id):
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
f"""
|
||||
UPDATE users
|
||||
SET otp_code = NULL, otp_expiration = NULL
|
||||
WHERE id = ?
|
||||
""",
|
||||
(user_id,)
|
||||
)
|
||||
if hasattr(conn, "commit"):
|
||||
conn.commit()
|
||||
|
||||
def update_password(self, email, password):
|
||||
'''Update user password'''
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('''
|
||||
UPDATE users SET password = ?
|
||||
WHERE email = ?
|
||||
''', (self.hash_password(password), email))
|
||||
conn.commit()
|
||||
|
||||
def hash_password(self, password: str) -> bytes:
|
||||
return hashlib.md5(password.encode('utf-8')).hexdigest()
|
||||
|
||||
def authenticate(self, email, password):
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("""
|
||||
SELECT * FROM users
|
||||
WHERE email = ? AND password = ?
|
||||
""", (email, self.hash_password(password)))
|
||||
row = cursor.fetchone()
|
||||
if not row:
|
||||
return None
|
||||
|
||||
return UserModel(
|
||||
id=row[0],
|
||||
workspace_id=row[1],
|
||||
first_name=row[2],
|
||||
last_name=row[3],
|
||||
email=row[4],
|
||||
address=row[6],
|
||||
profession=row[7],
|
||||
role=row[8],
|
||||
status=row[9],
|
||||
profile_pic=row[10],
|
||||
created_at=row[11],
|
||||
otp_code=row[12],
|
||||
otp_expiration=row[13],
|
||||
active=row[14]
|
||||
)
|
||||
|
||||
def register_user(self, email, password, workspace_id):
|
||||
"""Register a new user."""
|
||||
try:
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("""
|
||||
INSERT INTO users (workspace_id, email, password)
|
||||
VALUES (?, ?, ?)
|
||||
""", (workspace_id, email, self.hash_password(password)))
|
||||
conn.commit()
|
||||
return True
|
||||
except sqlite3.IntegrityError:
|
||||
return False # Username already exist
|
||||
|
||||
def add_user(self, user:UserModel):
|
||||
"""Create a new post."""
|
||||
try:
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
"""
|
||||
INSERT INTO users (workspace_id, first_name, last_name, email, password, address, profession, role, status, profile_pic)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
(user.workspace_id, user.first_name, user.last_name, user.email, user.password, user.address, user.profession, user.role, user.status, user.profile_pic),
|
||||
)
|
||||
conn.commit()
|
||||
return cursor.lastrowid
|
||||
except sqlite3.IntegrityError:
|
||||
return None
|
||||
|
||||
def get_user(self, user_id: int) -> UserModel | None:
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT * FROM users WHERE id = ?", (user_id,))
|
||||
row = cursor.fetchone()
|
||||
|
||||
if not row:
|
||||
return None
|
||||
|
||||
return UserModel(
|
||||
id=row[0],
|
||||
workspace_id=row[1],
|
||||
first_name=row[2],
|
||||
last_name=row[3],
|
||||
email=row[4],
|
||||
address=row[6],
|
||||
profession=row[7],
|
||||
role=row[8],
|
||||
status=row[9],
|
||||
profile_pic=row[10],
|
||||
created_at=row[11],
|
||||
otp_code=row[12],
|
||||
otp_expiration=row[13],
|
||||
active=row[14]
|
||||
)
|
||||
|
||||
def get_user_by_email(self, email: str) -> UserModel | None:
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT * FROM users WHERE email = ?", (email,))
|
||||
row = cursor.fetchone()
|
||||
|
||||
if not row:
|
||||
return None
|
||||
|
||||
return UserModel(
|
||||
id=row[0],
|
||||
workspace_id=row[1],
|
||||
first_name=row[2],
|
||||
last_name=row[3],
|
||||
email=row[4],
|
||||
password=row[5],
|
||||
address=row[6],
|
||||
profession=row[7],
|
||||
role=row[8],
|
||||
status=row[9],
|
||||
profile_pic=row[10],
|
||||
created_at=row[11],
|
||||
otp_code=row[12],
|
||||
otp_expiration=row[13],
|
||||
active=row[14]
|
||||
)
|
||||
|
||||
def get_users_by_workspace_id(self, workspace_id):
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT * FROM users WHERE workspace_id = ?", (workspace_id,))
|
||||
rows = cursor.fetchall()
|
||||
|
||||
return [
|
||||
UserModel(
|
||||
id=row[0],
|
||||
workspace_id=row[1],
|
||||
first_name=row[2],
|
||||
last_name=row[3],
|
||||
email=row[4],
|
||||
address=row[6],
|
||||
profession=row[7],
|
||||
role=row[8],
|
||||
status=row[9],
|
||||
profile_pic=row[10],
|
||||
created_at=row[11],
|
||||
otp_code=row[12],
|
||||
otp_expiration=row[13],
|
||||
active=row[14]
|
||||
)
|
||||
for row in rows
|
||||
]
|
||||
|
||||
def get_all_users(self):
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT * FROM users")
|
||||
rows = cursor.fetchall()
|
||||
|
||||
return [
|
||||
UserModel(
|
||||
id=row[0],
|
||||
workspace_id=row[1],
|
||||
first_name=row[2],
|
||||
last_name=row[3],
|
||||
email=row[4],
|
||||
address=row[6],
|
||||
profession=row[7],
|
||||
role=row[8],
|
||||
status=row[9],
|
||||
profile_pic=row[10],
|
||||
created_at=row[11],
|
||||
otp_code=row[12],
|
||||
otp_expiration=row[13],
|
||||
active=row[14]
|
||||
)
|
||||
for row in rows
|
||||
]
|
||||
|
||||
def update_user(self, user_id, first_name=None, last_name=None, email=None, password = None, address = None, profession = None, role = None, status = None, profile_pic=None, active=None):
|
||||
if first_name is None and last_name is None and email is None and password is None and address is None and profession is None and role is None and status is None and profile_pic is None and active is None:
|
||||
return False
|
||||
|
||||
fields = []
|
||||
params = []
|
||||
|
||||
if first_name is not None:
|
||||
fields.append("first_name = ?")
|
||||
params.append(first_name)
|
||||
if last_name is not None:
|
||||
fields.append("last_name = ?")
|
||||
params.append(last_name)
|
||||
if email is not None:
|
||||
fields.append("email = ?")
|
||||
params.append(email)
|
||||
if password is not None:
|
||||
fields.append("password = ?")
|
||||
params.append(password)
|
||||
if address is not None:
|
||||
fields.append("address = ?")
|
||||
params.append(address)
|
||||
if profession is not None:
|
||||
fields.append("profession = ?")
|
||||
params.append(profession)
|
||||
if role is not None:
|
||||
fields.append("role = ?")
|
||||
params.append(role)
|
||||
if status is not None:
|
||||
fields.append("status = ?")
|
||||
params.append(status)
|
||||
if profile_pic is not None:
|
||||
fields.append("profile_pic = ?")
|
||||
params.append(profile_pic)
|
||||
if active is not None:
|
||||
fields.append("active = ?")
|
||||
params.append(active)
|
||||
|
||||
params.append(user_id)
|
||||
query = f"UPDATE users SET {', '.join(fields)} WHERE id = ?"
|
||||
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(query, tuple(params))
|
||||
conn.commit()
|
||||
return cursor.rowcount > 0
|
||||
|
||||
#Do not use this method if you do not delete first in cascade, better use inactivate
|
||||
def delete_user(self, user_id):
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute("DELETE FROM users WHERE id = ?", (user_id,))
|
||||
|
||||
conn.commit()
|
||||
return cursor.rowcount > 0
|
||||
|
||||
def inactivate_user(self, user_id):
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute("UPDATE users SET status = ? WHERE id = ?", ('inactive',user_id,))
|
||||
|
||||
conn.commit()
|
||||
return cursor.rowcount > 0
|
||||
BIN
server/routes/__pycache__/auth.cpython-313.pyc
Normal file
BIN
server/routes/__pycache__/auth.cpython-313.pyc
Normal file
Binary file not shown.
BIN
server/routes/__pycache__/documents.cpython-313.pyc
Normal file
BIN
server/routes/__pycache__/documents.cpython-313.pyc
Normal file
Binary file not shown.
BIN
server/routes/__pycache__/payments.cpython-313.pyc
Normal file
BIN
server/routes/__pycache__/payments.cpython-313.pyc
Normal file
Binary file not shown.
BIN
server/routes/__pycache__/subscriptions.cpython-313.pyc
Normal file
BIN
server/routes/__pycache__/subscriptions.cpython-313.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
server/routes/__pycache__/users.cpython-313.pyc
Normal file
BIN
server/routes/__pycache__/users.cpython-313.pyc
Normal file
Binary file not shown.
295
server/routes/auth.py
Normal file
295
server/routes/auth.py
Normal file
@@ -0,0 +1,295 @@
|
||||
from flask import Blueprint, request, jsonify
|
||||
#from werkzeug.security import generate_password_hash, check_password_hash
|
||||
from utils.email import send_email, send_gmail
|
||||
from flask_jwt_extended import create_access_token, jwt_required, get_jwt_identity
|
||||
from flask_jwt_extended import decode_token
|
||||
import datetime
|
||||
import random
|
||||
import os
|
||||
from datetime import timezone
|
||||
import hashlib
|
||||
from models.users import Users
|
||||
from utils.welcome_email import WelcomeMessage
|
||||
from models.audit import Audit, AuditModel
|
||||
|
||||
auth_bp = Blueprint("auth", __name__)
|
||||
audit = Audit()
|
||||
|
||||
def hash_password(password: str) -> bytes:
|
||||
return hashlib.md5(password.encode('utf-8')).hexdigest()
|
||||
|
||||
@auth_bp.route("/register", methods=["POST"])
|
||||
def register():
|
||||
users = Users()
|
||||
data = request.get_json()
|
||||
email = data.get("email")
|
||||
password = data.get("password")
|
||||
workspace_id = data.get("workspace_id")
|
||||
|
||||
if not email or not password:
|
||||
return jsonify({"error": "Missing required fields"}), 400
|
||||
|
||||
existing_user = users.get_user_by_email(email)
|
||||
if existing_user:
|
||||
entry = AuditModel(action=f"Attempt to register an existing email:{email}", status='409 - User already exists')
|
||||
audit.new_entry(entry)
|
||||
return jsonify({"error": "User already exists"}), 409
|
||||
|
||||
users.register_user(email, password, workspace_id)
|
||||
|
||||
welcome_message = WelcomeMessage(email)
|
||||
welcome_message.send_email()
|
||||
entry = AuditModel(action=f"User registered successfully:{email}", status='201 - User created')
|
||||
audit.new_entry(entry)
|
||||
return jsonify({"message": "User registered successfully!"}), 201
|
||||
|
||||
@auth_bp.route("/login", methods=["POST"])
|
||||
def login():
|
||||
users = Users()
|
||||
data = request.get_json()
|
||||
email = data.get("email", "").strip().lower()
|
||||
password = data.get("password", "")
|
||||
|
||||
if not email or not password:
|
||||
entry = AuditModel(user_id=user.id, action=f"Attempt to login: {email}", status='400 - Missing email or password')
|
||||
audit.new_entry(entry)
|
||||
return jsonify({"error": "Missing email or password"}), 400
|
||||
|
||||
user = users.get_user_by_email(email)
|
||||
if not user or not hash_password(password)==user.password:
|
||||
print(user.password, password)
|
||||
entry = AuditModel(user_id=user.id ,action=f"Attempt to login: {email}", status='401 - Invalid credentials')
|
||||
audit.new_entry(entry)
|
||||
return jsonify({"error": "Invalid credentials"}), 401
|
||||
|
||||
if user.active != 1:
|
||||
entry = AuditModel(user_id=user.id, action=f"Attempt to login: {email}", status='401 - User inactive')
|
||||
audit.new_entry(entry)
|
||||
return jsonify({"error": "Inactive user"}), 401
|
||||
|
||||
otp_code = str(random.randint(100000, 999999))
|
||||
expiration = datetime.datetime.now(timezone.utc) + datetime.timedelta(minutes=10)
|
||||
users.update_user_otp(user.id, otp_code, expiration)
|
||||
|
||||
send_gmail(
|
||||
to_email=user.email,
|
||||
subject="Your Login Verification Code",
|
||||
body=f"Your login verification code is: {otp_code}"
|
||||
)
|
||||
entry = AuditModel(user_id=user.id, action=f"Attempt to login: {email}", status='200 - Verification code send!')
|
||||
audit.new_entry(entry)
|
||||
return jsonify({"message": "Verification code sent to your email."}), 200
|
||||
|
||||
|
||||
@auth_bp.route("/verify_code", methods=["POST"])
|
||||
def verify_code():
|
||||
users = Users()
|
||||
data = request.get_json()
|
||||
email = data.get("email", "").strip().lower()
|
||||
code = data.get("code", "")
|
||||
|
||||
if not email or not code:
|
||||
entry = AuditModel(action=f"Attempt to verify code: {email}", status='400 - Missing email or verification code!')
|
||||
audit.new_entry(entry)
|
||||
return jsonify({"error": "Missing email or verification code"}), 400
|
||||
|
||||
user = users.get_user_by_email(email)
|
||||
#-----------------------------------------------> for testing only remove in prod
|
||||
#if email != 'test@test.com':
|
||||
#-----------------------------------------------> for testing only remove in prod
|
||||
if not user or user.otp_code != code:
|
||||
entry = AuditModel(user_id=user.id, action=f"Attempt to verify code: {email}", status='401 - Invalid code!')
|
||||
audit.new_entry(entry)
|
||||
return jsonify({"error": "Invalid code"}), 401
|
||||
|
||||
exp = user.otp_expiration
|
||||
# Normalize to aware UTC datetime for safe comparison across SQLite (string) and Postgres (datetime)
|
||||
now_utc = datetime.datetime.now(timezone.utc)
|
||||
if isinstance(exp, str):
|
||||
try:
|
||||
exp_dt = datetime.datetime.fromisoformat(exp)
|
||||
except Exception:
|
||||
return jsonify({"error": "Invalid expiration format"}), 500
|
||||
if exp_dt.tzinfo is None:
|
||||
exp_dt = exp_dt.replace(tzinfo=timezone.utc)
|
||||
else:
|
||||
# Assume a datetime object from DB driver
|
||||
exp_dt = exp
|
||||
if exp_dt is None:
|
||||
entry = AuditModel(user_id=user.id, action=f"Attempt to verify code:{email}", status='500 - Missing expiration!')
|
||||
audit.new_entry(entry)
|
||||
return jsonify({"error": "Missing expiration"}), 500
|
||||
if exp_dt.tzinfo is None:
|
||||
exp_dt = exp_dt.replace(tzinfo=timezone.utc)
|
||||
if now_utc > exp_dt:
|
||||
entry = AuditModel(user_id=user.id, action=f"Attempt to verify code:{email}", status='403 - Verification code has expired!')
|
||||
audit.new_entry(entry)
|
||||
return jsonify({"error": "Verification code has expired"}), 403
|
||||
|
||||
users.clear_user_otp(user.id)
|
||||
|
||||
access_token = create_access_token(
|
||||
identity=str(user.id),
|
||||
expires_delta=datetime.timedelta(hours=12)
|
||||
)
|
||||
entry = AuditModel(user_id=user.id, action=f"Attempt to verify code:{email}", status='200 Ok - Login successful!')
|
||||
audit.new_entry(entry)
|
||||
return jsonify({
|
||||
"message": "Login successful",
|
||||
"access_token": access_token
|
||||
}), 200
|
||||
|
||||
|
||||
@auth_bp.route("/forgot_password", methods=["POST"])
|
||||
def forgot_password():
|
||||
users = Users()
|
||||
data = request.get_json()
|
||||
email = data.get("email", "").strip().lower()
|
||||
|
||||
if not email:
|
||||
entry = AuditModel(action=f"Attempt to recover password", status='400 - Email is required!')
|
||||
audit.new_entry(entry)
|
||||
return jsonify({"error": "Email is required"}), 400
|
||||
|
||||
user = users.get_user_by_email(email)
|
||||
if user:
|
||||
reset_token = create_access_token(
|
||||
identity=user["id"],
|
||||
expires_delta=datetime.timedelta(minutes=15),
|
||||
additional_claims={"purpose": "password_reset"}
|
||||
)
|
||||
|
||||
send_gmail(
|
||||
to_email=user["email"],
|
||||
subject="Password Reset Request",
|
||||
body=(
|
||||
"Click the link to reset your password: "
|
||||
f"{os.getenv('FRONTEND_BASE_URL', 'http://127.0.0.1:5100')}/reset_password?token={reset_token}"
|
||||
)
|
||||
)
|
||||
|
||||
entry = AuditModel(user_id=user.id, action=f"Attempt to recover password: {email}", status='200 Ok - If this email is registered, a reset link has been sent!')
|
||||
audit.new_entry(entry)
|
||||
return jsonify({"message": "If this email is registered, a reset link has been sent."}), 200
|
||||
|
||||
|
||||
@auth_bp.route("/reset_password", methods=["POST"])
|
||||
def reset_password():
|
||||
users = Users()
|
||||
data = request.get_json()
|
||||
token = data.get("token", "")
|
||||
new_password = data.get("new_password", "")
|
||||
|
||||
if not token or not new_password:
|
||||
entry = AuditModel( action=f"Attempt to recover password", status='400 - Missing token or new password!')
|
||||
audit.new_entry(entry)
|
||||
return jsonify({"error": "Missing token or new password"}), 400
|
||||
|
||||
try:
|
||||
decoded_token = decode_token(token)
|
||||
if decoded_token.get("purpose") != "password_reset":
|
||||
entry = AuditModel( action=f"Attempt to recover password", status='403 - Invalid token purpose!')
|
||||
audit.new_entry(entry)
|
||||
return jsonify({"error": "Invalid token purpose"}), 403
|
||||
except Exception:
|
||||
entry = AuditModel( action=f"Attempt to recover password", status='403 - Invalid or expired token!')
|
||||
audit.new_entry(entry)
|
||||
return jsonify({"error": "Invalid or expired token"}), 403
|
||||
|
||||
user_id = decoded_token["sub"]
|
||||
user = users.get_user(user_id)
|
||||
if not user:
|
||||
entry = AuditModel( action=f"Attempt to recover password", status='404 - User not found!')
|
||||
audit.new_entry(entry)
|
||||
return jsonify({"error": "User not found"}), 404
|
||||
|
||||
users.update_password(user_id, new_password)
|
||||
entry = AuditModel(user_id=user.id, action=f"Attempt to recover password:{user.email}", status='200 ok - Password has been reset successfully.')
|
||||
audit.new_entry(entry)
|
||||
return jsonify({"message": "Password has been reset successfully."}), 200
|
||||
|
||||
|
||||
@auth_bp.route("/me", methods=["GET"])
|
||||
@jwt_required()
|
||||
def me():
|
||||
users = Users()
|
||||
user_id = get_jwt_identity()
|
||||
user = users.get_user(user_id)
|
||||
if not user:
|
||||
entry = AuditModel( action=f"Get user data: ", status='404 - User not found.')
|
||||
audit.new_entry(entry)
|
||||
return jsonify({"error": "User not found"}), 404
|
||||
|
||||
entry = AuditModel(user_id=user.id, action=f"Get user data: {user.email}", status='200 Ok.')
|
||||
audit.new_entry(entry)
|
||||
return jsonify({
|
||||
'id': user.id,
|
||||
'workspace_id': user.workspace_id,
|
||||
'first_name':user.first_name,
|
||||
'last_name': user.last_name,
|
||||
'email':user.email,
|
||||
'address':user.address,
|
||||
'profession':user.profession,
|
||||
'role':user.role,
|
||||
'status': user.status,
|
||||
'profile_pic': user.profile_pic,
|
||||
'created_at': user.created_at,
|
||||
'otp_code': user.otp_code,
|
||||
'otp_expiration': user.otp_expiration
|
||||
}), 200
|
||||
|
||||
|
||||
# Validate token endpoint
|
||||
@auth_bp.route("/validate_token", methods=["GET"])
|
||||
@jwt_required()
|
||||
def validate_token():
|
||||
users = Users()
|
||||
user_id = get_jwt_identity()
|
||||
user = users.get_user(user_id)
|
||||
if not user:
|
||||
entry = AuditModel(action=f"Get access token:", status='404 - User not found.')
|
||||
audit.new_entry(entry)
|
||||
return jsonify({"error": "User not found"}), 404
|
||||
entry = AuditModel(user_id=user.id, action=f"Get access token: {user.email}", status='200 Ok - Token is valid')
|
||||
audit.new_entry(entry)
|
||||
return jsonify({"message": "Token is valid"}), 200
|
||||
|
||||
# @auth_bp.route("/temporary_password", methods=["POST"])
|
||||
# @jwt_required()
|
||||
# def change_passwd():
|
||||
# data = request.get_json()
|
||||
# if not data:
|
||||
# entry = AuditModel(action=f"Get temporary password token:", status='404 - Password not found.')
|
||||
# audit.new_entry(entry)
|
||||
# return jsonify({"error": "Password not found"}), 404
|
||||
# users = Users()
|
||||
# user_id = get_jwt_identity()
|
||||
# new_password_hash = generate_password_hash(data['password'])
|
||||
# users.update_user_password(user_id, new_password_hash)
|
||||
# users.update_temp_pass(user_id)
|
||||
# entry = AuditModel(user_id=user_id, action=f"Get temporary password:", status='200 Ok - Password has been updated successfully.')
|
||||
# audit.new_entry(entry)
|
||||
# return jsonify({"message": "Password has been updated successfully."}), 200
|
||||
|
||||
|
||||
@auth_bp.route("/update_passwrod", methods=["POST"])
|
||||
def update_passwrod():
|
||||
data = request.get_json()
|
||||
email = data.get("email", "").strip().lower()
|
||||
password = data.get('password')
|
||||
token = data.get('token')
|
||||
env_token = os.getenv('PASSWORD_TOKEN')
|
||||
if not email and not password and not token:
|
||||
entry = AuditModel(action=f"Update Password:", status='403 - Data not provided.')
|
||||
audit.new_entry(entry)
|
||||
return jsonify({"error": "Data not provided"}), 403
|
||||
if env_token != env_token:
|
||||
entry = AuditModel(action=f"Update Password:", status='401 - Invalid token.')
|
||||
audit.new_entry(entry)
|
||||
return jsonify({"error": "Data not provided"}), 401
|
||||
|
||||
users = Users()
|
||||
users.update_password(email, password)
|
||||
entry = AuditModel(action=f"Update Password:", status='200 - Password has been updated successfully.')
|
||||
audit.new_entry(entry)
|
||||
return jsonify({"message": "Password has been updated successfully."}), 200
|
||||
417
server/routes/documents.py
Normal file
417
server/routes/documents.py
Normal file
@@ -0,0 +1,417 @@
|
||||
import os
|
||||
import shutil
|
||||
from flask import Blueprint, request, jsonify, send_from_directory
|
||||
from flask_jwt_extended import jwt_required, get_jwt_identity
|
||||
from models.documents.documents_category import DocumentsCategory, DocumentsCategoryModel
|
||||
from models.documents.documents_standard import DocumentsStandard, DocumentsStandardModel
|
||||
from models.documents.documents_custom import DocumentsCustom, DocumentsCustomModel, CustomDocumentRequests, CustomDocumentRequestModel
|
||||
from models.audit import Audit, AuditModel
|
||||
|
||||
documents_bp = Blueprint("documents", __name__)
|
||||
audit = Audit()
|
||||
|
||||
# Definirea căii către folderul principal de documente (la nivelul server/client)
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
DOCUMENTS_ROOT = os.path.join(BASE_DIR, "documents")
|
||||
os.makedirs(DOCUMENTS_ROOT, exist_ok=True)
|
||||
|
||||
# --- Document Categories ---
|
||||
|
||||
@documents_bp.route("/categories/add", methods=["POST"])
|
||||
@jwt_required()
|
||||
def add_category():
|
||||
user_id = get_jwt_identity()
|
||||
data = request.get_json()
|
||||
name = data.get("name")
|
||||
access = data.get("access")
|
||||
|
||||
if not name:
|
||||
entry = AuditModel(user_id=user_id, action="Attempt to create category", status="400 - Missing name")
|
||||
audit.new_entry(entry)
|
||||
return jsonify({"error": "Missing name"}), 400
|
||||
|
||||
docs_cat = DocumentsCategory()
|
||||
# Filesafe: Verificăm dacă există deja o categorie cu acest nume (global)
|
||||
# pentru a preveni duplicatele în DB și conflictele în sistemul de fișiere.
|
||||
existing_categories = docs_cat.get_all_entries()
|
||||
for cat in existing_categories:
|
||||
if cat.name and cat.name.lower() == name.lower():
|
||||
return jsonify({"message": "Category already exists", "id": cat.id}), 200
|
||||
|
||||
category = DocumentsCategoryModel(user_id=user_id, name=name, access=access)
|
||||
result = docs_cat.new_entry(category)
|
||||
|
||||
if result:
|
||||
# Creare folder fizic pentru categorie
|
||||
category_path = os.path.join(DOCUMENTS_ROOT, name)
|
||||
os.makedirs(category_path, exist_ok=True)
|
||||
|
||||
entry = AuditModel(user_id=user_id, action=f"Category created: {name}", status="201 - Created")
|
||||
audit.new_entry(entry)
|
||||
return jsonify({"message": "Category created successfully", "id": result}), 201
|
||||
|
||||
return jsonify({"error": "Failed to create category"}), 500
|
||||
|
||||
@documents_bp.route("/categories", methods=["GET"])
|
||||
@jwt_required()
|
||||
def get_categories():
|
||||
docs_cat = DocumentsCategory()
|
||||
categories = docs_cat.get_all_entries()
|
||||
return jsonify([vars(c) for c in categories]), 200
|
||||
|
||||
@documents_bp.route("/categories/update/<int:id>", methods=["PUT"])
|
||||
@jwt_required()
|
||||
def update_category(id):
|
||||
user_id = get_jwt_identity()
|
||||
data = request.get_json()
|
||||
name = data.get("name")
|
||||
access = data.get("access")
|
||||
|
||||
if not name:
|
||||
return jsonify({"error": "Missing name"}), 400
|
||||
|
||||
docs_cat = DocumentsCategory()
|
||||
old_category = docs_cat.get_entry_by_id(id)
|
||||
|
||||
if not old_category:
|
||||
return jsonify({"error": "Category not found"}), 404
|
||||
|
||||
updated_count = docs_cat.update_entry(id, name, access)
|
||||
|
||||
if updated_count:
|
||||
# Redenumire folder dacă numele s-a schimbat
|
||||
if old_category.name != name:
|
||||
old_path = os.path.join(DOCUMENTS_ROOT, old_category.name)
|
||||
new_path = os.path.join(DOCUMENTS_ROOT, name)
|
||||
if os.path.exists(old_path):
|
||||
os.rename(old_path, new_path)
|
||||
else:
|
||||
os.makedirs(new_path, exist_ok=True)
|
||||
|
||||
entry = AuditModel(user_id=user_id, action=f"Category updated: {name} (ID: {id})", status="200 - OK")
|
||||
audit.new_entry(entry)
|
||||
return jsonify({"message": "Category updated successfully"}), 200
|
||||
|
||||
return jsonify({"error": "Category not found"}), 404
|
||||
|
||||
@documents_bp.route("/categories/delete/<int:id>", methods=["DELETE"])
|
||||
@jwt_required()
|
||||
def delete_category(id):
|
||||
user_id = get_jwt_identity()
|
||||
docs_cat = DocumentsCategory()
|
||||
|
||||
# Preluăm categoria înainte de ștergere pentru a cunoaște numele folderului
|
||||
category = docs_cat.get_entry_by_id(id)
|
||||
if not category:
|
||||
return jsonify({"error": "Category not found"}), 404
|
||||
|
||||
deleted_count = docs_cat.delete_entry(id)
|
||||
if deleted_count:
|
||||
# Ștergere folder fizic și tot conținutul său (documente încărcate)
|
||||
category_path = os.path.join(DOCUMENTS_ROOT, category.name)
|
||||
if os.path.exists(category_path):
|
||||
shutil.rmtree(category_path)
|
||||
|
||||
entry = AuditModel(user_id=user_id, action=f"Deleted Category: {category.name} (ID: {id})", status="200 - OK")
|
||||
audit.new_entry(entry)
|
||||
return jsonify({"message": "Category deleted successfully"}), 200
|
||||
|
||||
return jsonify({"error": "Category not found"}), 404
|
||||
|
||||
@documents_bp.route("/download", methods=["GET"])
|
||||
@jwt_required()
|
||||
def download_file():
|
||||
# Path-ul vine ca parametru (ex: NumeCategorie/fisier.pdf)
|
||||
file_path = request.args.get("path")
|
||||
if not file_path:
|
||||
return jsonify({"error": "Missing path"}), 400
|
||||
|
||||
# Securitate: prevenim ieșirea din DOCUMENTS_ROOT
|
||||
abs_path = os.path.abspath(os.path.join(DOCUMENTS_ROOT, file_path))
|
||||
if not abs_path.startswith(os.path.abspath(DOCUMENTS_ROOT)):
|
||||
return jsonify({"error": "Unauthorized access"}), 403
|
||||
|
||||
directory = os.path.dirname(abs_path)
|
||||
filename = os.path.basename(abs_path)
|
||||
return send_from_directory(directory, filename, as_attachment=True)
|
||||
|
||||
@documents_bp.route("/categories/refresh/<int:category_id>", methods=["POST"])
|
||||
@jwt_required()
|
||||
def refresh_category_files(category_id):
|
||||
user_id = get_jwt_identity()
|
||||
docs_cat = DocumentsCategory()
|
||||
docs_std = DocumentsStandard()
|
||||
|
||||
category = docs_cat.get_entry_by_id(category_id)
|
||||
if not category:
|
||||
return jsonify({"error": "Category not found"}), 404
|
||||
|
||||
category_path = os.path.join(DOCUMENTS_ROOT, category.name)
|
||||
if not os.path.exists(category_path):
|
||||
return jsonify({"error": "Category folder does not exist on disk"}), 404
|
||||
|
||||
# Obținem lista fișierelor de pe disc
|
||||
files_on_disk = [f for f in os.listdir(category_path) if os.path.isfile(os.path.join(category_path, f))]
|
||||
|
||||
# Obținem lista documentelor din DB pentru această categorie
|
||||
db_entries = docs_std.get_entries_by_category(category_id)
|
||||
db_file_paths = {entry.path for entry in db_entries}
|
||||
|
||||
added_count = 0
|
||||
for filename in files_on_disk:
|
||||
# Reconstruim path-ul așa cum este stocat în DB: "NumeCategorie/fisier.ext"
|
||||
relative_path = f"{category.name}/{filename}"
|
||||
|
||||
if relative_path not in db_file_paths:
|
||||
new_doc = DocumentsStandardModel(
|
||||
category_id=category_id,
|
||||
user_id=0, # User ID 0 pentru fișiere adăugate extern
|
||||
name=filename,
|
||||
path=relative_path,
|
||||
access=category.access
|
||||
)
|
||||
if docs_std.new_entry(new_doc):
|
||||
added_count += 1
|
||||
|
||||
if added_count > 0:
|
||||
audit.new_entry(AuditModel(user_id=user_id, action=f"Refreshed category {category.name}: added {added_count} files", status="200 - OK"))
|
||||
|
||||
return jsonify({"message": f"Refresh complete. Added {added_count} new documents.", "added": added_count}), 200
|
||||
|
||||
# --- Document Standards Category ---
|
||||
@documents_bp.route("/standards/category/<int:category_id>", methods=["GET"])
|
||||
@jwt_required()
|
||||
def get_standards_by_category(category_id):
|
||||
docs_std = DocumentsStandard()
|
||||
items = docs_std.get_entries_by_category(category_id)
|
||||
return jsonify([vars(i) for i in items]), 200
|
||||
|
||||
# --- Document Standards ---
|
||||
|
||||
@documents_bp.route("/standards/add", methods=["POST"])
|
||||
@jwt_required()
|
||||
def add_standard():
|
||||
user_id = get_jwt_identity()
|
||||
data = request.get_json()
|
||||
name = data.get("name")
|
||||
path = data.get("path")
|
||||
access = data.get("access")
|
||||
category_id = data.get("category_id")
|
||||
|
||||
if not name or not path or not category_id:
|
||||
entry = AuditModel(user_id=user_id, action="Attempt to create standard", status="400 - Missing fields")
|
||||
audit.new_entry(entry)
|
||||
return jsonify({"error": "Missing name, path or category_id"}), 400
|
||||
|
||||
# Mutare fizică a fișierului din folderul de upload în folderul categoriei
|
||||
# Sursa este folderul unde Flet salvează implicit upload-urile
|
||||
source_path = os.path.join(BASE_DIR, "client", "assets", "uploads", os.path.basename(path))
|
||||
dest_path = os.path.join(DOCUMENTS_ROOT, path)
|
||||
|
||||
if os.path.exists(source_path):
|
||||
os.makedirs(os.path.dirname(dest_path), exist_ok=True)
|
||||
shutil.move(source_path, dest_path)
|
||||
|
||||
docs_std = DocumentsStandard()
|
||||
standard = DocumentsStandardModel(user_id=user_id, category_id=category_id, name=name, path=path, access=access)
|
||||
result = docs_std.new_entry(standard)
|
||||
|
||||
if result:
|
||||
entry = AuditModel(user_id=user_id, action=f"Standard created: {name}", status="201 - Created")
|
||||
audit.new_entry(entry)
|
||||
return jsonify({"message": "Standard created successfully", "id": result}), 201
|
||||
|
||||
return jsonify({"error": "Failed to create standard"}), 500
|
||||
|
||||
@documents_bp.route("/standards", methods=["GET"])
|
||||
@jwt_required()
|
||||
def get_standards():
|
||||
user_id = get_jwt_identity()
|
||||
docs_std = DocumentsStandard()
|
||||
standards = docs_std.get_all_entries()
|
||||
return jsonify([vars(s) for s in standards]), 200
|
||||
|
||||
@documents_bp.route("/standards/delete/<int:id>", methods=["DELETE"])
|
||||
@jwt_required()
|
||||
def delete_standard(id):
|
||||
user_id = get_jwt_identity()
|
||||
docs_std = DocumentsStandard()
|
||||
|
||||
# Preluăm documentul înainte de ștergere pentru a cunoaște calea fișierului
|
||||
standard = docs_std.get_entry_by_id(id)
|
||||
if not standard:
|
||||
return jsonify({"error": "Standard document not found"}), 404
|
||||
|
||||
deleted_count = docs_std.delete_entry(id)
|
||||
if deleted_count:
|
||||
# Ștergere fișier fizic de pe disc
|
||||
file_path = os.path.join(DOCUMENTS_ROOT, standard.path)
|
||||
if os.path.exists(file_path):
|
||||
try:
|
||||
os.remove(file_path)
|
||||
except Exception as e:
|
||||
print(f"Error deleting physical file: {e}")
|
||||
|
||||
entry = AuditModel(user_id=user_id, action=f"Deleted Standard Document: {standard.name} (ID: {id})", status="200 - OK")
|
||||
audit.new_entry(entry)
|
||||
return jsonify({"message": "Standard document deleted successfully"}), 200
|
||||
|
||||
return jsonify({"error": "Failed to delete standard document"}), 500
|
||||
|
||||
# --- Document Custom ---
|
||||
|
||||
@documents_bp.route("/customs/add", methods=["POST"])
|
||||
@jwt_required()
|
||||
def add_custom():
|
||||
user_id = get_jwt_identity()
|
||||
data = request.get_json()
|
||||
name = data.get("name")
|
||||
path = data.get("path")
|
||||
access = data.get("access")
|
||||
|
||||
if not name or not path:
|
||||
entry = AuditModel(user_id=user_id, action="Attempt to create custom document", status="400 - Missing fields")
|
||||
audit.new_entry(entry)
|
||||
return jsonify({"error": "Missing name or path"}), 400
|
||||
|
||||
# Physical file move from upload folder to documents root
|
||||
source_path = os.path.join(BASE_DIR, "client", "assets", "uploads", os.path.basename(path))
|
||||
dest_path = os.path.join(DOCUMENTS_ROOT, path)
|
||||
|
||||
if os.path.exists(source_path):
|
||||
# Ensure the destination directory exists (for custom docs we usually put them in root or a 'custom' folder)
|
||||
os.makedirs(os.path.dirname(dest_path), exist_ok=True)
|
||||
shutil.move(source_path, dest_path)
|
||||
|
||||
docs_custom = DocumentsCustom()
|
||||
custom = DocumentsCustomModel(user_id=user_id, name=name, path=path, access=access)
|
||||
result = docs_custom.new_entry(custom)
|
||||
|
||||
if result:
|
||||
entry = AuditModel(user_id=user_id, action=f"Custom document created: {name}", status="201 - Created")
|
||||
audit.new_entry(entry)
|
||||
return jsonify({"message": "Custom document created successfully", "id": result}), 201
|
||||
|
||||
return jsonify({"error": "Failed to create custom document"}), 500
|
||||
|
||||
@documents_bp.route("/customs", methods=["GET"])
|
||||
@jwt_required()
|
||||
def get_customs():
|
||||
user_id = get_jwt_identity()
|
||||
docs_custom = DocumentsCustom()
|
||||
customs = docs_custom.get_entries_by_user_id(user_id)
|
||||
return jsonify([vars(c) for c in customs]), 200
|
||||
|
||||
@documents_bp.route("/customs/<int:id>", methods=["GET"])
|
||||
@jwt_required()
|
||||
def get_custom_by_id(id):
|
||||
docs_custom = DocumentsCustom()
|
||||
document = docs_custom.get_entry_by_id(id)
|
||||
if not document:
|
||||
return jsonify({"error": "Custom document not found"}), 404
|
||||
return jsonify(vars(document)), 200
|
||||
|
||||
@documents_bp.route("/customs/delete/<int:id>", methods=["DELETE"])
|
||||
@jwt_required()
|
||||
def delete_custom(id):
|
||||
user_id = get_jwt_identity()
|
||||
docs_custom = DocumentsCustom()
|
||||
deleted_count = docs_custom.delete_entry(id)
|
||||
if deleted_count:
|
||||
entry = AuditModel(user_id=user_id, action=f"Deleted Custom Document ID: {id}", status="200 - OK")
|
||||
audit.new_entry(entry)
|
||||
return jsonify({"message": "Custom document deleted successfully"}), 200
|
||||
|
||||
return jsonify({"error": "Custom document not found"}), 404
|
||||
|
||||
# --- Custom Document Requests ---
|
||||
|
||||
@documents_bp.route("/customs/requests/add", methods=["POST"])
|
||||
@jwt_required()
|
||||
def add_custom_request():
|
||||
user_id = get_jwt_identity()
|
||||
data = request.get_json()
|
||||
request_text = data.get("request_text")
|
||||
|
||||
if not request_text:
|
||||
return jsonify({"error": "Missing request text"}), 400
|
||||
|
||||
repo = CustomDocumentRequests()
|
||||
new_request = CustomDocumentRequestModel(
|
||||
client_id=user_id,
|
||||
request_text=request_text,
|
||||
status="new"
|
||||
)
|
||||
result = repo.new_entry(new_request)
|
||||
|
||||
if result:
|
||||
audit.new_entry(AuditModel(user_id=user_id, action=f"Custom request created: {result}", status="201 - Created"))
|
||||
return jsonify({"message": "Request submitted successfully", "id": result}), 201
|
||||
|
||||
return jsonify({"error": "Failed to submit request"}), 500
|
||||
|
||||
@documents_bp.route("/customs/requests", methods=["GET"])
|
||||
@jwt_required()
|
||||
def get_all_custom_requests():
|
||||
repo = CustomDocumentRequests()
|
||||
requests_list = repo.get_all_entries()
|
||||
return jsonify([vars(r) for r in requests_list]), 200
|
||||
|
||||
@documents_bp.route("/customs/requests/<int:id>", methods=["GET"])
|
||||
@jwt_required()
|
||||
def get_custom_request_by_id(id):
|
||||
repo = CustomDocumentRequests()
|
||||
req = repo.get_entry_by_id(id)
|
||||
if not req:
|
||||
return jsonify({"error": "Request not found"}), 404
|
||||
return jsonify(vars(req)), 200
|
||||
|
||||
@documents_bp.route("/customs/requests/client", methods=["GET"])
|
||||
@jwt_required()
|
||||
def get_client_custom_requests():
|
||||
user_id = get_jwt_identity()
|
||||
repo = CustomDocumentRequests()
|
||||
requests_list = repo.get_entries_by_client_id(user_id)
|
||||
return jsonify([vars(r) for r in requests_list]), 200
|
||||
|
||||
@documents_bp.route("/customs/requests/expert", methods=["GET"])
|
||||
@jwt_required()
|
||||
def get_expert_custom_requests():
|
||||
user_id = get_jwt_identity()
|
||||
repo = CustomDocumentRequests()
|
||||
requests_list = repo.get_entries_by_expert_id(user_id)
|
||||
return jsonify([vars(r) for r in requests_list]), 200
|
||||
|
||||
@documents_bp.route("/customs/requests/update/<int:id>", methods=["PUT"])
|
||||
@jwt_required()
|
||||
def update_custom_request(id):
|
||||
user_id = get_jwt_identity()
|
||||
data = request.get_json()
|
||||
|
||||
repo = CustomDocumentRequests()
|
||||
success = repo.update_entry(
|
||||
id=id,
|
||||
request_text=data.get("request_text"),
|
||||
status=data.get("status"),
|
||||
price=data.get("price"),
|
||||
expert_id=data.get("expert_id"),
|
||||
document_id=data.get("document_id")
|
||||
)
|
||||
|
||||
if success:
|
||||
audit.new_entry(AuditModel(user_id=user_id, action=f"Updated custom request ID: {id}", status="200 - OK"))
|
||||
return jsonify({"message": "Request updated successfully"}), 200
|
||||
|
||||
return jsonify({"error": "Request not found or no updates provided"}), 404
|
||||
|
||||
@documents_bp.route("/customs/requests/delete/<int:id>", methods=["DELETE"])
|
||||
@jwt_required()
|
||||
def delete_custom_request(id):
|
||||
user_id = get_jwt_identity()
|
||||
repo = CustomDocumentRequests()
|
||||
|
||||
if repo.delete_entry(id):
|
||||
audit.new_entry(AuditModel(user_id=user_id, action=f"Deleted custom request ID: {id}", status="200 - OK"))
|
||||
return jsonify({"message": "Request deleted successfully"}), 200
|
||||
|
||||
return jsonify({"error": "Request not found"}), 404
|
||||
85
server/routes/payments.py
Normal file
85
server/routes/payments.py
Normal file
@@ -0,0 +1,85 @@
|
||||
import os
|
||||
import shutil
|
||||
from flask import Blueprint, request, jsonify, send_from_directory
|
||||
from flask_jwt_extended import jwt_required, get_jwt_identity
|
||||
from models.payments.payments import Payments, PaymentsModel
|
||||
from models.audit import Audit, AuditModel
|
||||
|
||||
payments_bp = Blueprint("payments", __name__)
|
||||
audit = Audit()
|
||||
|
||||
@payments_bp.route("/add", methods=["POST"])
|
||||
@jwt_required()
|
||||
def add_payment():
|
||||
current_user_id = get_jwt_identity()
|
||||
data = request.get_json()
|
||||
|
||||
name = data.get("name")
|
||||
amount = data.get("amount")
|
||||
payment_type = data.get("type")
|
||||
|
||||
if not name or amount is None or not payment_type:
|
||||
return jsonify({"error": "Missing required fields (name, amount, type)"}), 400
|
||||
|
||||
payment_repo = Payments()
|
||||
new_payment = PaymentsModel(
|
||||
user_id=current_user_id,
|
||||
name=name,
|
||||
amount=float(amount),
|
||||
type=payment_type
|
||||
)
|
||||
|
||||
payment_id = payment_repo.add_payment(new_payment)
|
||||
if payment_id:
|
||||
audit.new_entry(AuditModel(user_id=current_user_id, action=f"Added payment: {name}", status="201 - Created"))
|
||||
return jsonify({"message": "Payment added successfully", "id": payment_id}), 201
|
||||
|
||||
return jsonify({"error": "Failed to add payment"}), 500
|
||||
|
||||
@payments_bp.route("/", methods=["GET"])
|
||||
@jwt_required()
|
||||
def get_payments():
|
||||
payment_repo = Payments()
|
||||
payments = payment_repo.get_all_payments()
|
||||
return jsonify([vars(p) for p in payments]), 200
|
||||
|
||||
@payments_bp.route("/<int:payment_id>", methods=["GET"])
|
||||
@jwt_required()
|
||||
def get_payment(payment_id):
|
||||
payment_repo = Payments()
|
||||
payment = payment_repo.get_payment(payment_id)
|
||||
if not payment:
|
||||
return jsonify({"error": "Payment not found"}), 404
|
||||
return jsonify(vars(payment)), 200
|
||||
|
||||
@payments_bp.route("/update/<int:payment_id>", methods=["PUT"])
|
||||
@jwt_required()
|
||||
def update_payment(payment_id):
|
||||
current_user_id = get_jwt_identity()
|
||||
data = request.get_json()
|
||||
payment_repo = Payments()
|
||||
|
||||
success = payment_repo.update_payment(
|
||||
payment_id,
|
||||
name=data.get("name"),
|
||||
amount=float(data.get("amount")) if data.get("amount") is not None else None,
|
||||
type=data.get("type")
|
||||
)
|
||||
|
||||
if success:
|
||||
audit.new_entry(AuditModel(user_id=current_user_id, action=f"Updated payment ID: {payment_id}", status="200 - OK"))
|
||||
return jsonify({"message": "Payment updated successfully"}), 200
|
||||
|
||||
return jsonify({"error": "Payment not found or no valid fields to update"}), 404
|
||||
|
||||
@payments_bp.route("/delete/<int:payment_id>", methods=["DELETE"])
|
||||
@jwt_required()
|
||||
def delete_payment(payment_id):
|
||||
current_user_id = get_jwt_identity()
|
||||
payment_repo = Payments()
|
||||
|
||||
if payment_repo.delete_payment(payment_id):
|
||||
audit.new_entry(AuditModel(user_id=current_user_id, action=f"Deleted payment ID: {payment_id}", status="200 - OK"))
|
||||
return jsonify({"message": "Payment deleted successfully"}), 200
|
||||
|
||||
return jsonify({"error": "Payment not found"}), 404
|
||||
21
server/routes/settings.py
Normal file
21
server/routes/settings.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from flask import Blueprint, request, jsonify
|
||||
#from werkzeug.security import generate_password_hash, check_password_hash
|
||||
from utils.email import send_email, send_gmail
|
||||
from flask_jwt_extended import create_access_token, jwt_required, get_jwt_identity
|
||||
|
||||
from models.audit import Audit, AuditModel
|
||||
|
||||
import os
|
||||
|
||||
auth_bp = Blueprint("settings", __name__)
|
||||
audit = Audit()
|
||||
|
||||
@auth_bp.route("/documente_juridice/add", methods=["POST"])
|
||||
@jwt_required()
|
||||
def documente_juridice_add():
|
||||
data = request.get_json()
|
||||
user_id = get_jwt_identity()
|
||||
if 'category' not in data:
|
||||
entry = AuditModel(user_id=user_id, action=f"Attempt to create a new category", status='400 - Missing category')
|
||||
audit.new_entry(entry)
|
||||
|
||||
71
server/routes/subscriptions.py
Normal file
71
server/routes/subscriptions.py
Normal file
@@ -0,0 +1,71 @@
|
||||
from flask import Blueprint, request, jsonify
|
||||
from flask_jwt_extended import jwt_required, get_jwt_identity
|
||||
from models.payments.subscriptions import Subscriptions, SubscriptionsModel
|
||||
from models.audit import Audit, AuditModel
|
||||
|
||||
subscriptions_bp = Blueprint("subscriptions", __name__)
|
||||
audit = Audit()
|
||||
|
||||
@subscriptions_bp.route("/add", methods=["POST"])
|
||||
@jwt_required()
|
||||
def add_subscription():
|
||||
current_user_id = get_jwt_identity()
|
||||
data = request.get_json()
|
||||
|
||||
name = data.get("name")
|
||||
pay_and_subs_id = data.get("pay_and_subs_id")
|
||||
mounts = data.get("mounts")
|
||||
|
||||
if not name or not pay_and_subs_id:
|
||||
return jsonify({"error": "Missing required fields (name, pay_and_subs_id)"}), 400
|
||||
|
||||
subs_repo = Subscriptions()
|
||||
new_sub = SubscriptionsModel(
|
||||
user_id=current_user_id,
|
||||
name=name,
|
||||
pay_and_subs_id=pay_and_subs_id,
|
||||
mounts=mounts
|
||||
)
|
||||
|
||||
sub_id = subs_repo.add_subscription(new_sub)
|
||||
if sub_id:
|
||||
audit.new_entry(AuditModel(user_id=current_user_id, action=f"Added subscription: {name}", status="201 - Created"))
|
||||
return jsonify({"message": "Subscription added successfully", "id": sub_id}), 201
|
||||
|
||||
return jsonify({"error": "Failed to add subscription"}), 500
|
||||
|
||||
@subscriptions_bp.route("/", methods=["GET"])
|
||||
@jwt_required()
|
||||
def get_all_subscriptions():
|
||||
subs_repo = Subscriptions()
|
||||
subscriptions = subs_repo.get_all_subscriptions()
|
||||
return jsonify([vars(s) for s in subscriptions]), 200
|
||||
|
||||
@subscriptions_bp.route("/<int:subscription_id>", methods=["GET"])
|
||||
@jwt_required()
|
||||
def get_subscription(subscription_id):
|
||||
subs_repo = Subscriptions()
|
||||
subscription = subs_repo.get_subscription(subscription_id)
|
||||
if not subscription:
|
||||
return jsonify({"error": "Subscription not found"}), 404
|
||||
return jsonify(vars(subscription)), 200
|
||||
|
||||
@subscriptions_bp.route("/update/<int:subscription_id>", methods=["PUT"])
|
||||
@jwt_required()
|
||||
def update_subscription(subscription_id):
|
||||
current_user_id = get_jwt_identity()
|
||||
data = request.get_json()
|
||||
subs_repo = Subscriptions()
|
||||
|
||||
success = subs_repo.update_subscription(
|
||||
subscription_id,
|
||||
name=data.get("name"),
|
||||
pay_and_subs_id=data.get("pay_and_subs_id"),
|
||||
mounts=data.get("mounts")
|
||||
)
|
||||
|
||||
if success:
|
||||
audit.new_entry(AuditModel(user_id=current_user_id, action=f"Updated subscription ID: {subscription_id}", status="200 - OK"))
|
||||
return jsonify({"message": "Subscription updated successfully"}), 200
|
||||
|
||||
return jsonify({"error": "Subscription not found or no valid fields to update"}), 404
|
||||
108
server/routes/users.py
Normal file
108
server/routes/users.py
Normal file
@@ -0,0 +1,108 @@
|
||||
from flask import Blueprint, request, jsonify
|
||||
from flask_jwt_extended import jwt_required, get_jwt_identity
|
||||
from models.users import Users, UserModel
|
||||
from models.audit import Audit, AuditModel
|
||||
|
||||
users_bp = Blueprint("users", __name__)
|
||||
audit = Audit()
|
||||
|
||||
@users_bp.route("/add", methods=["POST"])
|
||||
@jwt_required()
|
||||
def add_user():
|
||||
current_admin_id = get_jwt_identity()
|
||||
data = request.get_json()
|
||||
|
||||
email = data.get("email")
|
||||
workspace_id = data.get("workspace_id")
|
||||
|
||||
if not email or not workspace_id:
|
||||
return jsonify({"error": "Missing required fields (email, workspace_id)"}), 400
|
||||
|
||||
user_repo = Users()
|
||||
|
||||
if user_repo.get_user_by_email(email):
|
||||
return jsonify({"error": "User already exists"}), 409
|
||||
|
||||
new_user = UserModel(
|
||||
workspace_id=workspace_id,
|
||||
first_name=data.get("first_name"),
|
||||
last_name=data.get("last_name"),
|
||||
email=email,
|
||||
password=user_repo.hash_password(data.get("password")) if data.get("password") else None,
|
||||
address=data.get("address"),
|
||||
profession=data.get("profession"),
|
||||
role=data.get("role", "user"),
|
||||
status=data.get("status", "active"),
|
||||
profile_pic=data.get("profile_pic"),
|
||||
active=1
|
||||
)
|
||||
|
||||
user_id = user_repo.add_user(new_user)
|
||||
if user_id:
|
||||
audit.new_entry(AuditModel(user_id=current_admin_id, action=f"Added user: {email}", status="201 - Created"))
|
||||
return jsonify({"message": "User added successfully", "id": user_id}), 201
|
||||
|
||||
return jsonify({"error": "Failed to add user"}), 500
|
||||
|
||||
@users_bp.route("/<int:user_id>", methods=["GET"])
|
||||
@jwt_required()
|
||||
def get_user(user_id):
|
||||
user_repo = Users()
|
||||
user = user_repo.get_user(user_id)
|
||||
if not user:
|
||||
return jsonify({"error": "User not found"}), 404
|
||||
|
||||
# Convertim obiectul dataclass în dicționar pentru JSON
|
||||
return jsonify(vars(user)), 200
|
||||
|
||||
@users_bp.route("/", methods=["GET"])
|
||||
@jwt_required()
|
||||
def get_all_users():
|
||||
user_repo = Users()
|
||||
users = user_repo.get_all_users()
|
||||
|
||||
# Mapăm lista de obiecte UserModel la o listă de dicționare
|
||||
return jsonify([vars(u) for u in users]), 200
|
||||
|
||||
@users_bp.route("/update/<int:user_id>", methods=["PUT"])
|
||||
@jwt_required()
|
||||
def update_user(user_id):
|
||||
current_admin_id = get_jwt_identity()
|
||||
data = request.get_json()
|
||||
user_repo = Users()
|
||||
|
||||
# Dacă se dorește actualizarea parolei, o hash-uim înainte de salvare
|
||||
password = data.get("password")
|
||||
hashed_password = user_repo.hash_password(password) if password else None
|
||||
|
||||
success = user_repo.update_user(
|
||||
user_id,
|
||||
first_name=data.get("first_name"),
|
||||
last_name=data.get("last_name"),
|
||||
email=data.get("email"),
|
||||
password=hashed_password,
|
||||
address=data.get("address"),
|
||||
profession=data.get("profession"),
|
||||
role=data.get("role"),
|
||||
status=data.get("status"),
|
||||
profile_pic=data.get("profile_pic"),
|
||||
active=data.get("active")
|
||||
)
|
||||
|
||||
if success:
|
||||
audit.new_entry(AuditModel(user_id=current_admin_id, action=f"Updated user ID: {user_id}", status="200 - OK"))
|
||||
return jsonify({"message": "User updated successfully"}), 200
|
||||
|
||||
return jsonify({"error": "User not found or no valid fields to update"}), 404
|
||||
|
||||
@users_bp.route("/delete/<int:user_id>", methods=["DELETE"])
|
||||
@jwt_required()
|
||||
def delete_user(user_id):
|
||||
current_admin_id = get_jwt_identity()
|
||||
user_repo = Users()
|
||||
|
||||
if user_repo.delete_user(user_id):
|
||||
audit.new_entry(AuditModel(user_id=current_admin_id, action=f"Deleted user ID: {user_id}", status="200 - OK"))
|
||||
return jsonify({"message": "User deleted successfully"}), 200
|
||||
|
||||
return jsonify({"error": "User not found"}), 404
|
||||
BIN
server/utils/__pycache__/email.cpython-313.pyc
Normal file
BIN
server/utils/__pycache__/email.cpython-313.pyc
Normal file
Binary file not shown.
BIN
server/utils/__pycache__/welcome_email.cpython-313.pyc
Normal file
BIN
server/utils/__pycache__/welcome_email.cpython-313.pyc
Normal file
Binary file not shown.
139
server/utils/email.py
Normal file
139
server/utils/email.py
Normal file
@@ -0,0 +1,139 @@
|
||||
import smtplib
|
||||
from email.message import EmailMessage
|
||||
import os
|
||||
|
||||
def send_email(to_email, subject, body):
|
||||
smtp_host = os.environ.get("SMTP_HOST")
|
||||
smtp_port = int(os.environ.get("SMTP_PORT", 587))
|
||||
smtp_user = os.environ.get("SMTP_USER")
|
||||
smtp_pass = os.environ.get("SMTP_PASS")
|
||||
sender_email = os.environ.get("SMTP_FROM", smtp_user)
|
||||
|
||||
if not all([smtp_host, smtp_port, smtp_user, smtp_pass]):
|
||||
raise ValueError("SMTP config incomplete in environment variables.")
|
||||
|
||||
msg = EmailMessage()
|
||||
msg["Subject"] = subject
|
||||
msg["From"] = sender_email
|
||||
msg["To"] = to_email
|
||||
msg.set_content(body)
|
||||
|
||||
with smtplib.SMTP(smtp_host, smtp_port) as server:
|
||||
server.starttls()
|
||||
server.login(smtp_user, smtp_pass)
|
||||
server.send_message(msg)
|
||||
|
||||
|
||||
# Send email with attachment
|
||||
def send_email_with_attachment(to_email, subject, body, attachment_path):
|
||||
smtp_host = os.environ.get("SMTP_HOST")
|
||||
smtp_port = int(os.environ.get("SMTP_PORT", 587))
|
||||
smtp_user = os.environ.get("SMTP_USER")
|
||||
smtp_pass = os.environ.get("SMTP_PASS")
|
||||
sender_email = os.environ.get("SMTP_FROM", smtp_user)
|
||||
|
||||
if not all([smtp_host, smtp_port, smtp_user, smtp_pass]):
|
||||
raise ValueError("SMTP config incomplete in environment variables.")
|
||||
|
||||
msg = EmailMessage()
|
||||
msg["Subject"] = subject
|
||||
msg["From"] = sender_email
|
||||
msg["To"] = to_email
|
||||
msg.set_content(body)
|
||||
|
||||
if attachment_path and os.path.isfile(attachment_path):
|
||||
with open(attachment_path, "rb") as f:
|
||||
file_data = f.read()
|
||||
file_name = os.path.basename(attachment_path)
|
||||
msg.add_attachment(file_data, maintype="application", subtype="pdf", filename=file_name)
|
||||
else:
|
||||
raise FileNotFoundError(f"Attachment file not found: {attachment_path}")
|
||||
|
||||
with smtplib.SMTP(smtp_host, smtp_port) as server:
|
||||
server.starttls()
|
||||
server.login(smtp_user, smtp_pass)
|
||||
server.send_message(msg)
|
||||
|
||||
|
||||
# Send email using Gmail directly
|
||||
def send_gmail(to_email, subject, body):
|
||||
smtp_host = "smtp.gmail.com"
|
||||
smtp_port = 587
|
||||
smtp_user = 'macamete.robert@gmail.com'
|
||||
smtp_pass = 'advx yqlv jkaa czvr'
|
||||
sender_email = 'macamete.robert@gmail.com'
|
||||
|
||||
if not all([smtp_user, smtp_pass]):
|
||||
raise ValueError("GMAIL_USER and GMAIL_PASS must be set in environment variables.")
|
||||
|
||||
msg = EmailMessage()
|
||||
msg["Subject"] = subject
|
||||
msg["From"] = sender_email
|
||||
msg["To"] = to_email
|
||||
msg.set_content(body)
|
||||
|
||||
with smtplib.SMTP(smtp_host, smtp_port) as server:
|
||||
server.starttls()
|
||||
server.login(smtp_user, smtp_pass)
|
||||
server.send_message(msg)
|
||||
|
||||
|
||||
# Send email with attachment using Gmail directly
|
||||
def send_gmail_with_attachment(to_email, subject, body, attachment_path):
|
||||
smtp_host = "smtp.gmail.com"
|
||||
smtp_port = 587
|
||||
smtp_user = 'macamete.robert@gmail.com'
|
||||
smtp_pass = 'advx yqlv jkaa czvr'
|
||||
sender_email = 'macamete.robert@gmail.com'
|
||||
|
||||
if not all([smtp_user, smtp_pass]):
|
||||
raise ValueError("GMAIL_USER and GMAIL_PASS must be set in environment variables.")
|
||||
|
||||
msg = EmailMessage()
|
||||
msg["Subject"] = subject
|
||||
msg["From"] = sender_email
|
||||
msg["To"] = to_email
|
||||
msg.set_content(body)
|
||||
|
||||
if attachment_path and os.path.isfile(attachment_path):
|
||||
with open(attachment_path, "rb") as f:
|
||||
file_data = f.read()
|
||||
file_name = os.path.basename(attachment_path)
|
||||
msg.add_attachment(file_data, maintype="application", subtype="pdf", filename=file_name)
|
||||
else:
|
||||
raise FileNotFoundError(f"Attachment file not found: {attachment_path}")
|
||||
|
||||
with smtplib.SMTP(smtp_host, smtp_port) as server:
|
||||
server.starttls()
|
||||
server.login(smtp_user, smtp_pass)
|
||||
server.send_message(msg)
|
||||
|
||||
# Send email with attachment
|
||||
def send_custom_email_with_attachment(to_email, subject, body, attachment_path, SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASS):
|
||||
smtp_host = SMTP_HOST
|
||||
smtp_port = int(SMTP_PORT)
|
||||
smtp_user = SMTP_USER
|
||||
smtp_pass = SMTP_PASS
|
||||
sender_email = smtp_user
|
||||
|
||||
if not all([smtp_host, smtp_port, smtp_user, smtp_pass]):
|
||||
raise ValueError("SMTP config incomplete in environment variables.")
|
||||
|
||||
msg = EmailMessage()
|
||||
msg["Subject"] = subject
|
||||
msg["From"] = sender_email
|
||||
msg["To"] = to_email
|
||||
msg.set_content(body)
|
||||
|
||||
if attachment_path and os.path.isfile(attachment_path):
|
||||
with open(attachment_path, "rb") as f:
|
||||
file_data = f.read()
|
||||
file_name = os.path.basename(attachment_path)
|
||||
msg.add_attachment(file_data, maintype="application", subtype="pdf", filename=file_name)
|
||||
else:
|
||||
raise FileNotFoundError(f"Attachment file not found: {attachment_path}")
|
||||
|
||||
with smtplib.SMTP(smtp_host, smtp_port) as server:
|
||||
server.starttls()
|
||||
server.login(smtp_user, smtp_pass)
|
||||
server.send_message(msg)
|
||||
61
server/utils/welcome_email.py
Normal file
61
server/utils/welcome_email.py
Normal file
@@ -0,0 +1,61 @@
|
||||
import os
|
||||
import logging
|
||||
from utils.email import send_gmail_with_attachment
|
||||
|
||||
|
||||
class WelcomeMessage:
|
||||
"""
|
||||
Sends a welcome email with an optional attached user manual (PDF).
|
||||
|
||||
- Looks for the manual in SERVER_ASSETS_DIR (env) or defaults to ../assets next to this file.
|
||||
- Manual filename can be customized with WELCOME_MANUAL_FILENAME (env), default: manual.pdf.
|
||||
"""
|
||||
|
||||
def __init__(self, email: str):
|
||||
self.email = email
|
||||
self.subject = "Bine ati venit!"
|
||||
|
||||
# Allow overriding assets folder and manual filename via env for containerized deployments.
|
||||
default_assets = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "assets"))
|
||||
self.assets_folder = os.getenv("SERVER_ASSETS_DIR", default_assets)
|
||||
self.manual_filename = os.getenv("WELCOME_MANUAL_FILENAME", "Manual.pdf")
|
||||
self.manual = os.path.join(self.assets_folder, self.manual_filename)
|
||||
|
||||
logging.info("WelcomeMessage assets dir: %s", self.assets_folder)
|
||||
logging.info("WelcomeMessage manual path: %s", self.manual)
|
||||
|
||||
self.body = f"""
|
||||
|
||||
Stimate/ă domn/ă,
|
||||
|
||||
Ne face plăcere să vă urăm bun venit la JuridicBloc. Vă mulțumim că ați ales platforma noastră pentru a vă susține nevoile administratiei.
|
||||
|
||||
Pentru a vă ajuta să începeți, am atașat Manualul de utilizare la acest e-mail. Acesta oferă instrucțiuni pas cu pas privind configurarea contului, o prezentare generală a funcțiilor și cele mai bune practici pentru utilizarea eficientă a aplicatiei JuridicBloc.
|
||||
|
||||
Vă recomandăm să consultați manualul atunci când vă este convenabil pentru a vă familiariza cu capacitățile sistemului. Dacă aveți nevoie de asistență suplimentară, echipa noastră de asistență este disponibilă la adresa support@juridicbloc.ro.
|
||||
|
||||
Așteptăm cu nerăbdare să vă susținem succesul și să construim un parteneriat pe termen lung.
|
||||
|
||||
Cu sinceritate,
|
||||
Echipa JuridicBloc
|
||||
|
||||
"""
|
||||
|
||||
def send_email(self):
|
||||
# If the manual exists, send with attachment; otherwise, log and send nothing (or integrate a no-attachment sender later).
|
||||
if os.path.isfile(self.manual):
|
||||
send_gmail_with_attachment(
|
||||
to_email=self.email,
|
||||
subject=self.subject,
|
||||
body=self.body,
|
||||
attachment_path=self.manual,
|
||||
)
|
||||
else:
|
||||
logging.warning(
|
||||
f"Welcome manual missing, skipping attachment. Looked at: {self.manual} "
|
||||
f"(set SERVER_ASSETS_DIR or WELCOME_MANUAL_FILENAME to adjust). "
|
||||
f"Current working directory: {os.getcwd()}"
|
||||
)
|
||||
# If you later implement send_gmail() without attachment, call it here.
|
||||
# from utils.email import send_gmail
|
||||
# send_gmail(to_email=self.email, subject=self.subject, body=self.body)
|
||||
Reference in New Issue
Block a user