first commit

This commit is contained in:
Anurag Jangid 2026-10-08 02:44:15 +05:30
commit 6e354ba041
109 changed files with 12110 additions and 0 deletions

View file

@ -0,0 +1 @@

View file

@ -0,0 +1,256 @@
import re
import uuid
from datetime import UTC, datetime
from typing import Any, Literal
from motor.motor_asyncio import AsyncIOMotorDatabase
from pymongo import ReturnDocument
from app.schemas.document import CreateType, DocumentCreate, DocumentUpdate, MemberRole
COLLECTION = "documents"
SortField = Literal["updated_at", "last_visited_at"]
async def ensure_indexes(db: AsyncIOMotorDatabase) -> None:
col = db[COLLECTION]
await col.create_index([("owner_id", 1), ("updated_at", -1)])
await col.create_index([("owner_id", 1), ("last_visited_at", -1)])
await col.create_index([("owner_id", 1), ("stead_id", 1)])
await col.create_index([("owner_id", 1), ("type", 1)])
await col.create_index([("owner_id", 1), ("starred", 1)])
await col.create_index([("members.user_id", 1)])
await col.create_index([("title", "text"), ("created_by", "text")])
def _serialize(doc: dict[str, Any]) -> dict[str, Any]:
return {
"id": doc["_id"],
"type": doc["type"],
"title": doc["title"],
"template_id": doc.get("template_id"),
"stead_id": doc.get("stead_id"),
"content": doc.get("content", {}),
"created_by": doc["created_by"],
"created_at": doc["created_at"],
"updated_at": doc["updated_at"],
"starred": doc.get("starred", False),
"last_visited_at": doc.get("last_visited_at"),
"owner_id": doc.get("owner_id"),
"visibility": doc.get("visibility", "private"),
"members": doc.get("members", []),
}
def access_query(user_id: int) -> dict[str, Any]:
return {"$or": [{"owner_id": user_id}, {"members.user_id": user_id}]}
def can_edit(doc: dict[str, Any], user_id: int) -> bool:
if doc.get("owner_id") == user_id:
return True
for member in doc.get("members", []):
if member.get("user_id") == user_id and member.get("role") == "editor":
return True
return False
async def create_document(
db: AsyncIOMotorDatabase,
owner_id: int,
created_by: str,
data: DocumentCreate,
) -> dict[str, Any]:
now = datetime.now(UTC)
doc_id = str(uuid.uuid4())
doc = {
"_id": doc_id,
"owner_id": owner_id,
"type": data.type,
"title": data.title,
"template_id": data.template_id,
"stead_id": data.stead_id,
"content": data.content,
"created_by": created_by,
"created_at": now,
"updated_at": now,
"starred": False,
"last_visited_at": now,
"visibility": "private",
"members": [],
}
await db[COLLECTION].insert_one(doc)
return _serialize(doc)
async def get_document(
db: AsyncIOMotorDatabase,
doc_id: str,
user_id: int,
) -> dict[str, Any] | None:
doc = await db[COLLECTION].find_one({"_id": doc_id, **access_query(user_id)})
return _serialize(doc) if doc else None
async def list_documents(
db: AsyncIOMotorDatabase,
user_id: int,
doc_type: CreateType | None = None,
stead_id: str | None = None,
starred: bool | None = None,
sort: SortField = "updated_at",
) -> list[dict[str, Any]]:
query: dict[str, Any] = access_query(user_id)
extras: dict[str, Any] = {}
if doc_type:
extras["type"] = doc_type
if stead_id:
extras["stead_id"] = stead_id
if starred is not None:
extras["starred"] = starred
if extras:
query = {"$and": [query, extras]}
cursor = db[COLLECTION].find(query).sort(sort, -1)
return [_serialize(doc) async for doc in cursor]
async def search_documents(
db: AsyncIOMotorDatabase,
user_id: int,
q: str,
limit: int = 20,
) -> list[dict[str, Any]]:
term = q.strip()
if not term:
return []
pattern = re.escape(term)
regex = {"$regex": pattern, "$options": "i"}
query = {
"$and": [
access_query(user_id),
{
"$or": [
{"title": regex},
{"type": regex},
{"created_by": regex},
{"content.body": regex},
{"content.bodyHtml": regex},
]
},
]
}
cursor = db[COLLECTION].find(query).sort([("last_visited_at", -1), ("updated_at", -1)]).limit(limit)
return [_serialize(doc) async for doc in cursor]
async def update_document(
db: AsyncIOMotorDatabase,
doc_id: str,
user_id: int,
data: DocumentUpdate,
) -> dict[str, Any] | None:
existing = await db[COLLECTION].find_one({"_id": doc_id, **access_query(user_id)})
if existing is None:
return None
if not can_edit(existing, user_id):
return None
updates: dict[str, Any] = {"updated_at": datetime.now(UTC)}
if data.title is not None:
updates["title"] = data.title
if data.content is not None:
updates["content"] = data.content
if data.starred is not None:
updates["starred"] = data.starred
result = await db[COLLECTION].find_one_and_update(
{"_id": doc_id},
{"$set": updates},
return_document=ReturnDocument.AFTER,
)
return _serialize(result) if result else None
async def record_visit(
db: AsyncIOMotorDatabase,
doc_id: str,
user_id: int,
) -> dict[str, Any] | None:
now = datetime.now(UTC)
result = await db[COLLECTION].find_one_and_update(
{"_id": doc_id, **access_query(user_id)},
{"$set": {"last_visited_at": now}},
return_document=ReturnDocument.AFTER,
)
return _serialize(result) if result else None
async def add_member(
db: AsyncIOMotorDatabase,
doc_id: str,
owner_id: int,
member: dict[str, Any],
role: MemberRole,
) -> dict[str, Any] | None:
existing = await db[COLLECTION].find_one({"_id": doc_id, "owner_id": owner_id})
if existing is None:
return None
members = list(existing.get("members", []))
members = [m for m in members if m.get("user_id") != member["user_id"]]
members.append(
{
"user_id": member["user_id"],
"role": role,
"name": member.get("name", ""),
"email": member.get("email", ""),
}
)
result = await db[COLLECTION].find_one_and_update(
{"_id": doc_id, "owner_id": owner_id},
{
"$set": {
"members": members,
"visibility": "members",
"updated_at": datetime.now(UTC),
}
},
return_document=ReturnDocument.AFTER,
)
return _serialize(result) if result else None
async def remove_member(
db: AsyncIOMotorDatabase,
doc_id: str,
owner_id: int,
member_user_id: int,
) -> dict[str, Any] | None:
existing = await db[COLLECTION].find_one({"_id": doc_id, "owner_id": owner_id})
if existing is None:
return None
members = [m for m in existing.get("members", []) if m.get("user_id") != member_user_id]
visibility = "members" if members else "private"
result = await db[COLLECTION].find_one_and_update(
{"_id": doc_id, "owner_id": owner_id},
{
"$set": {
"members": members,
"visibility": visibility,
"updated_at": datetime.now(UTC),
}
},
return_document=ReturnDocument.AFTER,
)
return _serialize(result) if result else None
async def delete_document(
db: AsyncIOMotorDatabase,
doc_id: str,
owner_id: int,
) -> bool:
result = await db[COLLECTION].delete_one({"_id": doc_id, "owner_id": owner_id})
return result.deleted_count > 0

View file

@ -0,0 +1,80 @@
from sqlalchemy.orm import Session
from app.models.user import PasswordResetToken, User
from app.schemas.user import UserCreate, UserUpdate
from app.utils.security import hash_password, verify_password
def get_user_by_id(db: Session, user_id: int) -> User | None:
return db.query(User).filter(User.id == user_id).first()
def get_user_by_email(db: Session, email: str) -> User | None:
return db.query(User).filter(User.email == email).first()
def get_user_by_username(db: Session, username: str) -> User | None:
return db.query(User).filter(User.username == username).first()
def get_user_by_email_or_username(db: Session, identifier: str) -> User | None:
return (
db.query(User)
.filter((User.email == identifier) | (User.username == identifier))
.first()
)
def create_user(db: Session, data: UserCreate) -> User:
user = User(
email=data.email.lower(),
username=data.username.lower(),
full_name=data.full_name,
employee_id=data.employee_id,
phone=data.phone,
occupation=data.occupation,
hashed_password=hash_password(data.password),
)
db.add(user)
db.commit()
db.refresh(user)
return user
def update_user(db: Session, user: User, data: UserUpdate) -> User:
update_data = data.model_dump(exclude_unset=True)
for field, value in update_data.items():
setattr(user, field, value)
db.commit()
db.refresh(user)
return user
def authenticate_user(db: Session, identifier: str, password: str) -> User | None:
user = get_user_by_email_or_username(db, identifier)
if user is None or not verify_password(password, user.hashed_password):
return None
return user
def create_password_reset_token(db: Session, user: User, token: str, expires_at) -> PasswordResetToken:
reset = PasswordResetToken(user_id=user.id, token=token, expires_at=expires_at)
db.add(reset)
db.commit()
db.refresh(reset)
return reset
def get_valid_reset_token(db: Session, token: str) -> PasswordResetToken | None:
from datetime import UTC, datetime
reset = (
db.query(PasswordResetToken)
.filter(
PasswordResetToken.token == token,
PasswordResetToken.used.is_(False),
PasswordResetToken.expires_at > datetime.now(UTC),
)
.first()
)
return reset