Text Extraction Done, UI Fixes Done, Extracted Template Layout Saved in DB

This commit is contained in:
2026-07-12 14:24:17 +05:30
parent 3163bb213e
commit 3d6614f408
79 changed files with 3139 additions and 81 deletions

38
docengine/app/core/aes.py Normal file
View File

@@ -0,0 +1,38 @@
import base64
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
def decrypt(base64_input: str, secret: bytes) -> str | None:
try:
combined = base64.b64decode(base64_input)
# IV length is 12 bytes in the Java implementation
iv_length = 12
if len(combined) <= iv_length + 16:
return None
iv = combined[:iv_length]
# Java AES/GCM appends a 16-byte authentication tag at the end of cipherBytes
# cryptography library expects it to be passed into the modes.GCM(iv, tag)
cipher_bytes_with_tag = combined[iv_length:]
actual_ciphertext = cipher_bytes_with_tag[:-16]
tag = cipher_bytes_with_tag[-16:]
cipher = Cipher(algorithms.AES(secret), modes.GCM(iv, tag))
decryptor = cipher.decryptor()
plain_bytes = decryptor.update(actual_ciphertext) + decryptor.finalize()
return plain_bytes.decode('utf-8')
except Exception:
return None
def get_string_value(encrypted_id: str, secret: str, secret_key_internal: str) -> str | None:
if not secret or not secret_key_internal:
return None
try:
# Replicate Java's logic: (s + secretKeyInternal).substring(0, Math.min(..., 16))
combined_str = secret + secret_key_internal
combined_secret = combined_str[:16].encode('utf-8')
return decrypt(encrypted_id, combined_secret)
except Exception:
return None

View File

@@ -28,30 +28,32 @@ class Settings(BaseSettings):
# Database
db_host: str = "192.168.0.111"
db_port: int = 7925
db_name: str = "document_engine"
db_port: int = 5432
db_name: str = "ocr"
db_user: str = "postgres"
db_password: str = "M@tr!x#149@dm!N"
db_password: str = "M@triXPostgr3s@6202"
db_schema: str = "admin"
db_pool_size: int = 20
db_max_overflow: int = 10
db_echo: bool = False
# Redis
redis_host: str = "localhost"
redis_port: int = 6379
redis_host: str = "192.168.0.111"
redis_port: int = 7901
redis_db: int = 0
redis_password: str = ""
redis_password: str = "M@triXR3d1s@6202"
# Celery
celery_broker_url: str = "redis://localhost:6379/0"
celery_result_backend: str = "redis://localhost:6379/1"
celery_broker_url: str = "redis://:M@triXR3d1s@6202@192.168.0.111:7901/0"
celery_result_backend: str = "redis://:M@triXR3d1s@6202@192.168.0.111:7901/1"
# JWT
jwt_secret_key: str = "a7f3c9e1d4b8f2a6c0e5d7b3a9f1c4e8d2b6a0f5c3e7d1b9a4f8c2e6d0b5a3"
jwt_algorithm: str = "HS256"
jwt_access_token_expire_minutes: int = 30
jwt_refresh_token_expire_days: int = 7
session_encryption_secret: str = ""
session_encryption_secret_internal: str = ""
# Storage
storage_provider: str = "local"
@@ -67,7 +69,7 @@ class Settings(BaseSettings):
log_format: str = "json"
# CORS
cors_origins: list[str] = ["http://localhost:3000", "http://localhost:8080"]
cors_origins: list[str] = ["http://localhost:3000", "http://localhost:8080", "http://localhost:4200"]
cors_allow_credentials: bool = True
# Rate Limiting

View File

@@ -11,17 +11,63 @@ from app.core.security import InvalidTokenError, decode_token
from app.models.user import User
from app.repositories.user_repository import UserRepository
security_scheme = HTTPBearer(auto_error=True)
import uuid
from app.core.config import settings
security_scheme = HTTPBearer(auto_error=False)
def get_current_user(
credentials: Annotated[HTTPAuthorizationCredentials, Depends(security_scheme)],
credentials: Annotated[HTTPAuthorizationCredentials | None, Depends(security_scheme)],
db: Annotated[Session, Depends(get_db)],
) -> User:
"""Extract and validate the current user from the JWT token."""
if credentials is None:
if settings.app_env == "development":
# Auto-login as default dev admin user if no token provided in dev
user_repo = UserRepository(db)
user = db.query(User).first()
if user:
return user
dev_user = User(
id=uuid.uuid4(),
username="dev_admin",
email="admin@docengine.local",
hashed_password="mock_password",
is_active=True,
is_superuser=True,
)
db.add(dev_user)
db.commit()
db.refresh(dev_user)
return dev_user
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Not authenticated",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = decode_token(credentials.credentials)
except InvalidTokenError:
if settings.app_env == "development":
# Fallback to dev admin user on token decode failure in dev
user_repo = UserRepository(db)
user = db.query(User).first()
if user:
return user
dev_user = User(
id=uuid.uuid4(),
username="dev_admin",
email="admin@docengine.local",
hashed_password="mock_password",
is_active=True,
is_superuser=True,
)
db.add(dev_user)
db.commit()
db.refresh(dev_user)
return dev_user
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid or expired token",

View File

@@ -8,6 +8,7 @@ from jose import JWTError, jwt
from passlib.context import CryptContext
from app.core.config import settings
from app.core.aes import get_string_value
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
@@ -44,8 +45,14 @@ def create_refresh_token(data: dict[str, Any], expires_delta: timedelta | None =
def decode_token(token: str) -> dict[str, Any]:
"""Decode and validate a JWT token."""
decrypted_token = get_string_value(
token,
settings.session_encryption_secret,
settings.session_encryption_secret_internal
)
final_token = decrypted_token if decrypted_token else token
try:
payload = jwt.decode(token, settings.jwt_secret_key, algorithms=[settings.jwt_algorithm])
payload = jwt.decode(final_token, settings.jwt_secret_key, algorithms=[settings.jwt_algorithm])
return payload
except JWTError as e:
raise InvalidTokenError(str(e)) from e

View File

@@ -8,7 +8,7 @@ logger = get_logger(__name__)
def on_startup() -> None:
"""Application startup event handler."""
setup_logging()
logger.info("application_starting", event="startup")
logger.info("application_starting", phase="startup")
# Ensure storage directories exist
from app.storage.provider import get_storage_provider
@@ -25,15 +25,15 @@ def on_startup() -> None:
else:
logger.error("database_connection_failed")
logger.info("application_started", event="startup_complete")
logger.info("application_started", phase="startup_complete")
def on_shutdown() -> None:
"""Application shutdown event handler."""
logger.info("application_shutting_down", event="shutdown")
logger.info("application_shutting_down", phase="shutdown")
# Cleanup resources
from app.core.database import engine
engine.dispose()
logger.info("application_stopped", event="shutdown_complete")
logger.info("application_stopped", phase="shutdown_complete")

View File

@@ -37,9 +37,9 @@ app = FastAPI(
)
# Setup middleware (order matters: last added = first executed)
setup_cors(app)
app.add_middleware(AuditMiddleware)
app.add_middleware(RateLimitMiddleware)
setup_cors(app)
# Setup Prometheus metrics
setup_metrics(app)

View File

@@ -13,13 +13,7 @@ def setup_cors(app: FastAPI) -> None:
allow_origins=settings.cors_origins,
allow_credentials=settings.cors_allow_credentials,
allow_methods=["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
allow_headers=[
"Authorization",
"Content-Type",
"Accept",
"X-Request-ID",
"X-Requested-With",
],
allow_headers=["*"],
expose_headers=[
"X-Request-ID",
"X-Process-Time",

View File

@@ -3,7 +3,7 @@ from __future__ import annotations
import uuid
from datetime import datetime
from sqlalchemy import Boolean, DateTime, ForeignKey, String, Table, Text, func
from sqlalchemy import Boolean, Column, DateTime, ForeignKey, String, Table, Text, func
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import Mapped, mapped_column, relationship
@@ -13,8 +13,8 @@ from app.models.base import TimestampMixin, UUIDPrimaryKeyMixin
user_roles_table = Table(
"user_roles",
Base.metadata,
mapped_column("user_id", UUID(as_uuid=True), ForeignKey("users.id", ondelete="CASCADE"), primary_key=True),
mapped_column("role_id", UUID(as_uuid=True), ForeignKey("roles.id", ondelete="CASCADE"), primary_key=True),
Column("user_id", UUID(as_uuid=True), ForeignKey("users.id", ondelete="CASCADE"), primary_key=True),
Column("role_id", UUID(as_uuid=True), ForeignKey("roles.id", ondelete="CASCADE"), primary_key=True),
)

View File

@@ -40,4 +40,9 @@ celery_app.conf.update(
},
)
celery_app.autodiscover_tasks(["app.tasks"])
celery_app.conf.update(
imports=[
"app.tasks.document_tasks",
"app.tasks.maintenance_tasks"
]
)