pdf product converted

pull/1/head
ahmed.mujtaba 2026-08-03 19:55:01 +05:00
parent c47a380d3d
commit a9f0b32eea
11 changed files with 282 additions and 112 deletions

View File

@ -103,6 +103,7 @@ from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
import sqlmodel # SQLModel renders AutoString() into migrations but adds no import
${imports if imports else ""}
revision: str = ${repr(up_revision)}

View File

@ -31,6 +31,7 @@ from sqlalchemy.ext.asyncio import (
create_async_engine,
)
from sqlalchemy.orm import DeclarativeBase
from sqlmodel import SQLModel
from dotenv import load_dotenv
load_dotenv()
@ -124,6 +125,13 @@ class Base(DeclarativeBase):
)
# SQLModel keeps its own registry, so `SQLModel` tables would be invisible to the
# Alembic autogenerate in alembic_setup.py, which diffs `Base.metadata` alone.
# Pointing SQLModel at the same MetaData gives both styles one registry, and lets
# SQLModel tables inherit the naming convention and the default schema above.
SQLModel.metadata = Base.metadata
_engine: AsyncEngine | None = None
_sessionmaker: async_sessionmaker[AsyncSession] | None = None

Binary file not shown.

View File

@ -19,25 +19,22 @@ _DEFAULT_OUT_DIR = Path(__file__).resolve().parent / "decoded_attachments"
def _decode_bytes(attachment: dict) -> bytes:
"""base64 -> raw bytes, with an integrity check against declared size."""
"""base64 -> raw bytes.
Graph's ``size`` often includes MIME/encoding overhead and may not equal
``len(contentBytes)`` after decode, so it is not treated as a hard check.
"""
b64 = attachment.get("contentBytes")
if not b64:
raise AttachmentDecodeError(f"{attachment.get('name')!r}: no contentBytes")
try:
raw = base64.b64decode(b64, validate=True)
return base64.b64decode(b64, validate=True)
except binascii.Error as exc:
raise AttachmentDecodeError(
f"{attachment.get('name')!r}: bad base64: {exc}"
) from exc
declared = attachment.get("size")
if declared is not None and len(raw) != declared:
raise AttachmentDecodeError(
f"{attachment.get('name')!r}: declared {declared} B, decoded {len(raw)} B"
)
return raw
def _write(out_dir: Path, name: str, raw: bytes) -> Path:
out_dir = Path(out_dir)

View File

@ -1,73 +1,103 @@
from sqlalchemy import Column, Integer, String, ForeignKey, DateTime, Boolean, UUID
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.orm import relationship
from sqlmodel import SQLModel
from datetime import datetime
from users.models import Users
from sqlalchemy.ext.asyncio import AsyncSession
import uuid
from datetime import datetime
from typing import Any, Optional
class Inbox(SQLModel,table=True):
id=Column(Integer,primary_key=True,autoincrement=True)
user_id = Column(UUID,ForeignKey("users.id"))
from sqlalchemy import Column
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.ext.asyncio import AsyncSession
from sqlmodel import Field, Relationship, SQLModel
alert_id=Column(UUID,ForeignKey("inbox_alerts.id"))
alerts=relationship("Inbox_Alerts",back_populates="inbox",foreign_keys=[alert_id])
message_id=Column(UUID,ForeignKey("inbox_messages.id"))
messages=relationship("Inbox_Messages",back_populates="inbox",foreign_keys=[message_id])
created_at=Column(DateTime,default=datetime.now)
updated_at=Column(DateTime,default=datetime.now)
# is_active=Column(Boolean,default=True)
# is_deleted=Column(Boolean,default=False)
# user=relationship("Users",back_populates="inbox",foreign_keys=[user_id])
from users.models import Users
class Inbox_Alerts(SQLModel,table=True):
id=Column(UUID,primary_key=True,default=uuid.uuid4)
alert_sender_name=Column(String,nullable=False)
alert_sender_email=Column(String,nullable=False)
is_read=Column(Boolean,default=False)
recieve_time=Column(DateTime,default=datetime.now)
class Inbox_Messages(SQLModel,table=True):
id=Column(UUID,primary_key=True,default=uuid.uuid4)
full_email_response=Column(JSONB,nullable=True)
message_subject=Column(String,nullable=False)
message_body=Column(String,nullable=False)
message_sent_time=Column(String,nullable=False)
# message_status=Column(String,nullable=False)
message_received_time=Column(String,nullable=False)
message_from=Column(String,nullable=False)
message_to=Column(String,nullable=False)
message_cc=Column(String,nullable=True)
message_bcc=Column(String,nullable=True)
message_attachments=Column(JSONB,nullable=True)
message_read=Column(Boolean,default=False)
attachment=Column(Boolean,default=False)
message_reply=Column(String,nullable=True)
file_path=Column(String,nullable=True)
class Inbox(SQLModel, table=True):
__tablename__ = "inbox"
id: int | None = Field(default=None, primary_key=True)
user_id: uuid.UUID | None = Field(default=None, foreign_key="users.id")
alert_id: uuid.UUID | None = Field(default=None, foreign_key="inbox_alerts.id")
alerts: Optional["Inbox_Alerts"] = Relationship(back_populates="inbox")
message_id: uuid.UUID | None = Field(default=None, foreign_key="inbox_messages.id")
messages: Optional["Inbox_Messages"] = Relationship(back_populates="inbox")
created_at: datetime = Field(default_factory=datetime.now)
updated_at: datetime = Field(default_factory=datetime.now)
# is_active: bool = Field(default=True)
# is_deleted: bool = Field(default=False)
# user: Users | None = Relationship(back_populates="inbox")
class Inbox_Alerts(SQLModel, table=True):
__tablename__ = "inbox_alerts"
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
alert_sender_name: str
alert_sender_email: str
is_read: bool = Field(default=False)
recieve_time: datetime = Field(default_factory=datetime.now)
inbox: list[Inbox] = Relationship(back_populates="alerts")
class Inbox_Messages(SQLModel, table=True):
__tablename__ = "inbox_messages"
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
full_email_response: dict[str, Any] | None = Field(
default=None, sa_column=Column(JSONB)
)
message_subject: str
message_body: str
message_sent_time: str
message_received_time: str
message_from: str
message_to: str
message_cc: str | None = Field(default=None)
message_bcc: str | None = Field(default=None)
message_read: bool = Field(default=False)
attachment: bool = Field(default=False)
message_reply: str | None = Field(default=None)
file_path: str | None = Field(default=None)
inbox: list[Inbox] = Relationship(back_populates="messages")
@staticmethod
def _body_text(email_data: dict) -> str:
body = email_data.get("body")
if isinstance(body, dict):
return body.get("content") or ""
if isinstance(body, str):
return body
return email_data.get("bodyPreview") or ""
@classmethod
async def insert_email(cls,session:AsyncSession,email_data:dict):
email=cls(
message_subject=email_data.get("subject"),
message_body=email_data.get("body"),
# message_type=email_data.get("type"),
message_sent_time=email_data.get("sentDateTime"),
message_read=email_data.get("isRead"),
message_received_time=email_data.get("receivedDateTime"),
message_from=email_data.get("from").get("emailAddress").get("address"),
message_to=",".join([r["emailAddress"]["address"] for r in email_data.get("toRecipients", [])]),
message_cc=",".join([r["emailAddress"]["address"] for r in email_data.get("ccRecipients", [])]),
message_bcc=",".join([r["emailAddress"]["address"] for r in email_data.get("bccRecipients", [])]),
message_attachments=email_data.get("attachments"),
attachment=email_data.get("hasAttachments"),
message_reply=",".join([r["emailAddress"]["address"] for r in email_data.get("replyTo", [])]),
async def insert_email(cls, session: AsyncSession, email_data: dict):
email = cls(
message_subject=email_data.get("subject") or "",
message_body=cls._body_text(email_data),
message_sent_time=email_data.get("sentDateTime") or "",
message_read=bool(email_data.get("isRead")),
message_received_time=email_data.get("receivedDateTime") or "",
message_from=email_data.get("from", {})
.get("emailAddress", {})
.get("address", ""),
message_to=",".join(
[r["emailAddress"]["address"] for r in email_data.get("toRecipients", [])]
),
message_cc=",".join(
[r["emailAddress"]["address"] for r in email_data.get("ccRecipients", [])]
),
message_bcc=",".join(
[r["emailAddress"]["address"] for r in email_data.get("bccRecipients", [])]
),
attachment=bool(email_data.get("hasAttachments")),
message_reply=",".join(
[r["emailAddress"]["address"] for r in email_data.get("replyTo", [])]
),
full_email_response=email_data,
)
session.add(email)
await session.commit()
return email
return email

View File

@ -11,6 +11,7 @@ from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
import sqlmodel # SQLModel renders AutoString() into migrations but adds no import
${imports if imports else ""}
revision: str = ${repr(up_revision)}

View File

@ -0,0 +1,104 @@
"""initial roles users inbox
Revision ID: 817ded7e4f55
Revises:
Create Date: 2026-08-03 14:16:25.739300+00:00
"""
from __future__ import annotations
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
import sqlmodel
from sqlalchemy.dialects import postgresql
revision: str = '817ded7e4f55'
down_revision: Union[str, None] = None
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('inbox_alerts',
sa.Column('id', sa.Uuid(), nullable=False),
sa.Column('alert_sender_name', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
sa.Column('alert_sender_email', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
sa.Column('is_read', sa.Boolean(), nullable=False),
sa.Column('recieve_time', sa.DateTime(), nullable=False),
sa.PrimaryKeyConstraint('id', name=op.f('pk_inbox_alerts')),
schema='app'
)
op.create_table('inbox_messages',
sa.Column('id', sa.Uuid(), nullable=False),
sa.Column('full_email_response', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
sa.Column('message_subject', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
sa.Column('message_body', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
sa.Column('message_sent_time', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
sa.Column('message_received_time', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
sa.Column('message_from', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
sa.Column('message_to', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
sa.Column('message_cc', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
sa.Column('message_bcc', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
sa.Column('message_attachments', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
sa.Column('message_read', sa.Boolean(), nullable=False),
sa.Column('attachment', sa.Boolean(), nullable=False),
sa.Column('message_reply', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
sa.Column('file_path', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
sa.PrimaryKeyConstraint('id', name=op.f('pk_inbox_messages')),
schema='app'
)
op.create_table('roles',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('role_name', sa.Enum('SYSTEM_ADMINISTRATOR', 'HR_ADMINISTRATOR', 'RECRUITER', 'HIRING_MANAGER', 'DEPARTMENT_HEAD', 'INTERVIEWER', 'CEO', 'CANDIDATE', name='enumroles'), nullable=False),
sa.Column('description', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
sa.Column('permissions', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
sa.Column('created_at', sa.DateTime(), nullable=False),
sa.Column('updated_at', sa.DateTime(), nullable=False),
sa.Column('is_active', sa.Boolean(), nullable=False),
sa.Column('is_deleted', sa.Boolean(), nullable=False),
sa.PrimaryKeyConstraint('id', name=op.f('pk_roles')),
sa.UniqueConstraint('role_name', name=op.f('uq_roles_role_name')),
schema='app'
)
op.create_table('users',
sa.Column('id', sa.Uuid(), nullable=False),
sa.Column('name', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
sa.Column('email', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
sa.Column('role_id', sa.Integer(), nullable=True),
sa.Column('password', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=False),
sa.Column('updated_at', sa.DateTime(), nullable=False),
sa.Column('is_active', sa.Boolean(), nullable=False),
sa.Column('is_deleted', sa.Boolean(), nullable=False),
sa.ForeignKeyConstraint(['role_id'], ['app.roles.id'], name=op.f('fk_users_role_id_roles')),
sa.PrimaryKeyConstraint('id', name=op.f('pk_users')),
sa.UniqueConstraint('email', name=op.f('uq_users_email')),
schema='app'
)
op.create_table('inbox',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('user_id', sa.Uuid(), nullable=True),
sa.Column('alert_id', sa.Uuid(), nullable=True),
sa.Column('message_id', sa.Uuid(), nullable=True),
sa.Column('created_at', sa.DateTime(), nullable=False),
sa.Column('updated_at', sa.DateTime(), nullable=False),
sa.ForeignKeyConstraint(['alert_id'], ['app.inbox_alerts.id'], name=op.f('fk_inbox_alert_id_inbox_alerts')),
sa.ForeignKeyConstraint(['message_id'], ['app.inbox_messages.id'], name=op.f('fk_inbox_message_id_inbox_messages')),
sa.ForeignKeyConstraint(['user_id'], ['app.users.id'], name=op.f('fk_inbox_user_id_users')),
sa.PrimaryKeyConstraint('id', name=op.f('pk_inbox')),
schema='app'
)
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('inbox', schema='app')
op.drop_table('users', schema='app')
op.drop_table('roles', schema='app')
op.drop_table('inbox_messages', schema='app')
op.drop_table('inbox_alerts', schema='app')
# ### end Alembic commands ###

View File

@ -0,0 +1,32 @@
"""remove_message_attachments
Revision ID: ea9c09868aff
Revises: 817ded7e4f55
Create Date: 2026-08-03 14:52:42.420029+00:00
"""
from __future__ import annotations
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
import sqlmodel # SQLModel renders AutoString() into migrations but adds no import
from sqlalchemy.dialects import postgresql
revision: str = 'ea9c09868aff'
down_revision: Union[str, None] = '817ded7e4f55'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('inbox_messages', 'message_attachments', schema='app')
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('inbox_messages', sa.Column('message_attachments', postgresql.JSONB(astext_type=sa.Text()), autoincrement=False, nullable=True), schema='app')
# ### end Alembic commands ###

View File

@ -1,12 +1,13 @@
from sqlalchemy import Column, Integer, String, DateTime, Boolean, Enum as SAEnum
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.orm import relationship
from sqlmodel import SQLModel
from datetime import datetime
from enum import Enum
from typing import Any
from sqlalchemy import Column, Enum as SAEnum
from sqlalchemy.dialects.postgresql import JSONB
from sqlmodel import Field, Relationship, SQLModel
class EnumRoles(Enum):
class EnumRoles(str, Enum):
SYSTEM_ADMINISTRATOR = "system_administrator"
HR_ADMINISTRATOR = "hr_administrator"
RECRUITER = "recruiter"
@ -17,14 +18,19 @@ class EnumRoles(Enum):
CANDIDATE = "candidate"
class Roles(SQLModel,table=True):
class Roles(SQLModel, table=True):
__tablename__ = "roles"
id = Column(Integer, primary_key=True,autoincrement=True)
role_name = Column(SAEnum(EnumRoles),nullable=False,unique=True,default=EnumRoles.SYSTEM_ADMINISTRATOR)
description = Column(String,nullable=True,default="System Administrator")
permissions = Column(JSONB,nullable=True)
created_at = Column(DateTime,default=datetime.now)
updated_at = Column(DateTime,default=datetime.now)
is_active = Column(Boolean,default=True)
is_deleted = Column(Boolean,default=False)
users = relationship("Users", back_populates="role")
id: int | None = Field(default=None, primary_key=True)
role_name: EnumRoles = Field(
default=EnumRoles.SYSTEM_ADMINISTRATOR,
sa_column=Column(SAEnum(EnumRoles), nullable=False, unique=True),
)
description: str | None = Field(default="System Administrator")
permissions: dict[str, Any] | None = Field(default=None, sa_column=Column(JSONB))
created_at: datetime = Field(default_factory=datetime.now)
updated_at: datetime = Field(default_factory=datetime.now)
is_active: bool = Field(default=True)
is_deleted: bool = Field(default=False)
users: list["Users"] = Relationship(back_populates="role")

View File

@ -3,12 +3,12 @@ from fastapi.responses import JSONResponse
from fastapi import HTTPException
from db_setup import get_session
from sqlalchemy.ext.asyncio import AsyncSession
from inbox.views import Email
from users.models import Users
from dotenv import load_dotenv
load_dotenv()
router = APIRouter()
@router.post("/users/create")
async def create_user(user: User, session: AsyncSession = Depends(get_session)):
async def create_user(user: Users, session: AsyncSession = Depends(get_session)):
pass

View File

@ -1,30 +1,21 @@
from sqlalchemy import Column, Integer, String, ForeignKey, DateTime, Boolean, UUID
from sqlalchemy.orm import relationship
import uuid
from datetime import datetime
from sqlmodel import SQLModel
from sqlmodel import Field, Relationship, SQLModel
from role.models import Roles
class Users(SQLModel,table=True):
class Users(SQLModel, table=True):
__tablename__ = "users"
id = Column(UUID, primary_key=True,default=uuid.uuid4)
name = Column(String,nullable=False)
email = Column(String,nullable=False,unique=True)
role_id = Column(Integer,ForeignKey("roles.id"))
role = relationship("Roles",back_populates="users",foreign_keys=[role_id])
password = Column(String,nullable=False)
created_at = Column(DateTime,default=datetime.now)
updated_at = Column(DateTime,default=datetime.now)
is_active = Column(Boolean,default=True)
is_deleted = Column(Boolean,default=False)
# role = Column(String,nullable=False)
# company_id = Column(UUID,ForeignKey("companies.id"))
# company = relationship("Companies",back_populates="users")
# created_by = Column(UUID,ForeignKey("users.id"))
# created_by_user = relationship("Users",back_populates="users")
# updated_by = Column(UUID,ForeignKey("users.id"))
# updated_by_user = relationship("Users",back_populates="users")
# deleted_by = Column(UUID,ForeignKey("users.id"))
# deleted_by_user = relationship("Users",back_populates="users")
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
name: str
email: str = Field(unique=True)
role_id: int | None = Field(default=None, foreign_key="roles.id")
role: Roles | None = Relationship(back_populates="users")
password: str
created_at: datetime = Field(default_factory=datetime.now)
updated_at: datetime = Field(default_factory=datetime.now)
is_active: bool = Field(default=True)
is_deleted: bool = Field(default=False)