Initial upload
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
from fastapi import APIRouter, Request, Form, HTTPException
|
||||
from fastapi.responses import RedirectResponse, HTMLResponse
|
||||
from starlette.middleware.sessions import SessionMiddleware
|
||||
from starlette.responses import PlainTextResponse
|
||||
from .. import config, jmap
|
||||
from fastapi.templating import Jinja2Templates
|
||||
from jinja2 import FileSystemLoader, Environment, select_autoescape
|
||||
import pathlib, base64, os
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
templates = Jinja2Templates(directory=str(pathlib.Path(__file__).resolve().parent.parent / "templates"))
|
||||
|
||||
def make_csrf(session: dict) -> str:
|
||||
token = base64.urlsafe_b64encode(os.urandom(24)).decode()
|
||||
session["csrf"] = token
|
||||
return token
|
||||
|
||||
def check_csrf(session: dict, token: str):
|
||||
if not token or token != session.get("csrf"):
|
||||
raise HTTPException(status_code=400, detail="CSRF token invalid")
|
||||
|
||||
@router.get("/login", response_class=HTMLResponse)
|
||||
async def login_form(request: Request):
|
||||
csrf = make_csrf(request.session)
|
||||
return templates.TemplateResponse("login.html", {"request": request, "csrf": csrf, "jmap_base": config.JMAP_BASE})
|
||||
|
||||
@router.post("/login")
|
||||
async def login_submit(request: Request, username: str = Form(...), password: str = Form(...), jmap_base: str = Form(...), csrf: str = Form(...)):
|
||||
check_csrf(request.session, csrf)
|
||||
async with jmap.client() as ac:
|
||||
try:
|
||||
session = await jmap.get_session(ac, jmap_base, username, password)
|
||||
except PermissionError:
|
||||
raise HTTPException(status_code=401, detail="Invalid credentials")
|
||||
api_url = session.get("apiUrl") or jmap_base
|
||||
download_url = session.get("downloadUrl") or ""
|
||||
primary = session.get("primaryAccounts") or {}
|
||||
request.session["user"] = {"username": username, "jmap_base": jmap_base, "api_url": api_url, "auth": (username, password), "download_url": download_url, "primary": primary, "session": session}
|
||||
return RedirectResponse("/mail", status_code=303)
|
||||
|
||||
@router.get("/logout")
|
||||
async def logout(request: Request):
|
||||
request.session.clear()
|
||||
return RedirectResponse("/login", status_code=303)
|
||||
@@ -0,0 +1,39 @@
|
||||
from fastapi import APIRouter, Request, Depends, HTTPException
|
||||
from fastapi.responses import HTMLResponse
|
||||
from fastapi.templating import Jinja2Templates
|
||||
import pathlib, datetime
|
||||
from .. import jmap
|
||||
from ..utils import fmt_when
|
||||
|
||||
router = APIRouter()
|
||||
templates = Jinja2Templates(directory=str(pathlib.Path(__file__).resolve().parent.parent / "templates"))
|
||||
|
||||
def require_user(request: Request):
|
||||
user = request.session.get("user")
|
||||
if not user:
|
||||
raise HTTPException(status_code=401)
|
||||
return user
|
||||
|
||||
@router.get("/calendar", response_class=HTMLResponse)
|
||||
async def calendar(request: Request, user=Depends(require_user)):
|
||||
async with jmap.client() as ac:
|
||||
api = user["api_url"]
|
||||
account_id = None
|
||||
now = datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc)
|
||||
until = now + datetime.timedelta(days=30)
|
||||
res = await jmap.call(ac, api, tuple(user["auth"]), [
|
||||
["CalendarEvent/query", {"accountId": account_id, "limit": 200, "sort":[{"property":"start","isAscending": True}]}, "q1"],
|
||||
["CalendarEvent/get", {"accountId": account_id, "#ids": {"resultOf":"q1","name":"CalendarEvent/query","path":"ids"}, "properties":["id","title","start","end","location"]}, "g1"]
|
||||
])
|
||||
events = []
|
||||
for name, data, _ in res.get("methodResponses", []):
|
||||
if name == "CalendarEvent/get":
|
||||
for e in data.get("list", []):
|
||||
try:
|
||||
s = datetime.datetime.fromisoformat((e.get("start") or "").replace("Z","+00:00"))
|
||||
if s < now - datetime.timedelta(days=1) or s > until:
|
||||
continue
|
||||
except Exception:
|
||||
pass
|
||||
events.append({"title": e.get("title") or "(no title)", "start": fmt_when(e.get("start")), "end": fmt_when(e.get("end")), "loc": e.get("location")})
|
||||
return templates.TemplateResponse("calendar.html", {"request": request, "events": events, "user": user})
|
||||
@@ -0,0 +1,35 @@
|
||||
from typing import Optional
|
||||
from fastapi import APIRouter, Request, Depends, HTTPException
|
||||
from fastapi.responses import HTMLResponse
|
||||
from fastapi.templating import Jinja2Templates
|
||||
import pathlib
|
||||
from .. import jmap
|
||||
|
||||
router = APIRouter()
|
||||
templates = Jinja2Templates(directory=str(pathlib.Path(__file__).resolve().parent.parent / "templates"))
|
||||
|
||||
def require_user(request: Request):
|
||||
user = request.session.get("user")
|
||||
if not user:
|
||||
raise HTTPException(status_code=401)
|
||||
return user
|
||||
|
||||
@router.get("/contacts", response_class=HTMLResponse)
|
||||
async def contacts(request: Request, q: Optional[str] = None, user=Depends(require_user)):
|
||||
async with jmap.client() as ac:
|
||||
api = user["api_url"]
|
||||
account_id = None
|
||||
filter_cond = {"text": q} if q else {}
|
||||
res = await jmap.call(ac, api, tuple(user["auth"]), [
|
||||
["Contact/query", {"accountId": account_id, "filter": filter_cond, "limit": 100}, "c1"],
|
||||
["Contact/get", {"accountId": account_id, "#ids": {"resultOf":"c1","name":"Contact/query","path":"ids"}, "properties":["id","firstName","lastName","emails","company"]}, "c2"]
|
||||
])
|
||||
contacts = []
|
||||
for name, data, _ in res.get("methodResponses", []):
|
||||
if name == "Contact/get":
|
||||
for c in data.get("list", []):
|
||||
emails = [e.get("email","") for e in (c.get("emails") or [])]
|
||||
contacts.append({"name": f"{c.get('firstName','')} {c.get('lastName','')}".strip() or (emails[0] if emails else ""),
|
||||
"email": ", ".join(emails),
|
||||
"org": c.get("company")})
|
||||
return templates.TemplateResponse("contacts.html", {"request": request, "contacts": contacts, "q": q, "user": user})
|
||||
@@ -0,0 +1,316 @@
|
||||
import json
|
||||
import io
|
||||
import bleach
|
||||
from typing import Optional
|
||||
from fastapi import APIRouter, Request, Depends, HTTPException, Form, UploadFile, File
|
||||
from fastapi.responses import HTMLResponse, RedirectResponse, StreamingResponse, JSONResponse
|
||||
from fastapi.templating import Jinja2Templates
|
||||
import pathlib
|
||||
from .. import jmap
|
||||
from ..utils import human_size, fmt_when
|
||||
|
||||
router = APIRouter()
|
||||
templates = Jinja2Templates(directory=str(pathlib.Path(__file__).resolve().parent.parent / "templates"))
|
||||
|
||||
def require_user(request: Request):
|
||||
user = request.session.get("user")
|
||||
if not user:
|
||||
raise HTTPException(status_code=401)
|
||||
return user
|
||||
|
||||
def make_csrf(session: dict) -> str:
|
||||
import os, base64
|
||||
token = base64.urlsafe_b64encode(os.urandom(24)).decode()
|
||||
session["csrf"] = token
|
||||
return token
|
||||
|
||||
def check_csrf(session: dict, token: str):
|
||||
if not token or token != session.get("csrf"):
|
||||
raise HTTPException(status_code=400, detail="CSRF token invalid")
|
||||
|
||||
@router.get("/mail", response_class=HTMLResponse)
|
||||
async def inbox(request: Request, q: Optional[str] = None, mailbox: Optional[str] = None, user=Depends(require_user)):
|
||||
async with jmap.client() as ac:
|
||||
api = user["api_url"]
|
||||
primary = user.get("primary", {})
|
||||
account_id = primary.get("urn:ietf:params:jmap:mail")
|
||||
boxes, inbox_id = await get_mailboxes(ac, api, tuple(user["auth"]), account_id)
|
||||
box_id = mailbox or inbox_id
|
||||
filt = {"text": q} if q else ({"inMailbox": box_id} if box_id else {})
|
||||
res = await jmap.call(ac, api, tuple(user["auth"]), [
|
||||
["Email/query", {"accountId": account_id, "filter": filt, "sort": [{"property":"receivedAt","isAscending": False}], "limit": 50}, "c1"],
|
||||
["Email/get", {"accountId": account_id, "#ids": {"resultOf":"c1","name":"Email/query","path":"ids"}, "properties": ["id","subject","from","size","receivedAt"]}, "c2"]
|
||||
])
|
||||
emails = []
|
||||
|
||||
for name, data, _ in res.get("methodResponses", []):
|
||||
if name == "Email/get":
|
||||
for e in data.get("list", []):
|
||||
from_str = ", ".join([a.get("name") or a.get("email","") for a in (e.get("from") or [])])
|
||||
emails.append({"id": e["id"], "subject": e.get("subject") or "(no subject)", "from": from_str, "when": fmt_when(e.get("receivedAt")), "size": human_size(e.get("size"))})
|
||||
return templates.TemplateResponse("mail.html", {"request": request, "messages": emails, "q": q, "user": user, "mailboxes": boxes, "selected": box_id})
|
||||
|
||||
@router.get("/mail/{email_id}", response_class=HTMLResponse)
|
||||
async def read_message(request: Request, email_id: str, user=Depends(require_user)):
|
||||
async with jmap.client() as ac:
|
||||
api = user["api_url"]
|
||||
res = await jmap.call(ac, api, tuple(user["auth"]), [
|
||||
["Email/get", {"ids": [email_id], "properties": ["id","subject","from","to","receivedAt","size","keywords","preview","bodyStructure","htmlBody","textBody"]}, "c1"]
|
||||
])
|
||||
msg = {"id": email_id, "subject":"", "from":"", "to":[], "when":"", "textBody":"", "htmlBody":"", "attachments":[]}
|
||||
bstruct = None
|
||||
cid_map = {}
|
||||
for name, data, _ in res.get("methodResponses", []):
|
||||
if name == "Email/get":
|
||||
lst = data.get("list", [])
|
||||
if lst:
|
||||
e = lst[0]
|
||||
msg["subject"] = e.get("subject") or msg["subject"]
|
||||
msg["from"] = ", ".join([a.get("name") or a.get("email","") for a in (e.get("from") or [])]) or msg["from"]
|
||||
msg["to"] = [a.get("email","") for a in (e.get("to") or [])] or msg["to"]
|
||||
msg["when"] = fmt_when(e.get("receivedAt")) or msg["when"]
|
||||
if "textBody" in e:
|
||||
msg["textBody"] = e.get("textBody") or msg["textBody"]
|
||||
if "htmlBody" in e:
|
||||
raw_html = e.get("htmlBody")
|
||||
if raw_html:
|
||||
msg["htmlBody"] = bleach.clean(raw_html, tags=bleach.sanitizer.ALLOWED_TAGS.union({"p","span","div","br","hr","pre","code","blockquote","ul","ol","li","table","thead","tbody","tr","th","td","img","a","b","i","strong","em"}), attributes={"a":["href","title"],"img":["src","alt","title","width","height"]}, strip=True)
|
||||
bstruct = bstruct or e.get("bodyStructure")
|
||||
def walk_cid(bs):
|
||||
if not isinstance(bs, dict): return
|
||||
cid = bs.get("cid")
|
||||
if cid and bs.get("blobId"):
|
||||
cid_map[cid.strip("<>")] = {"blobId": bs["blobId"], "name": bs.get("name") or "inline"}
|
||||
for p in bs.get("subParts", []) or []:
|
||||
walk_cid(p)
|
||||
if bstruct:
|
||||
walk_cid(bstruct)
|
||||
|
||||
def walk_bs(bs, out):
|
||||
if not isinstance(bs, dict): return
|
||||
if bs.get("disposition") == "attachment":
|
||||
out.append({"name": bs.get("name") or "attachment", "type": bs.get("type") or "application/octet-stream", "size": bs.get("size"), "blobId": bs.get("blobId")})
|
||||
for p in bs.get("subParts", []) or []:
|
||||
walk_bs(p, out)
|
||||
att = []
|
||||
walk_bs(bstruct, att)
|
||||
msg["attachments"] = att
|
||||
# Inline CID images via internal route
|
||||
if msg.get("htmlBody") and cid_map:
|
||||
import re as _re
|
||||
def _repl(m):
|
||||
cid = m.group(1)
|
||||
return f'src="/mail/{email_id}/cid/{cid}"'
|
||||
msg["htmlBody"] = _re.sub(r'src=\"cid:([^\"]+)\"', _repl, msg["htmlBody"]) # cid_rewrite
|
||||
return templates.TemplateResponse("message.html", {"request": request, "msg": msg, "user": user})
|
||||
|
||||
@router.get("/compose", response_class=HTMLResponse)
|
||||
async def compose_form(request: Request, user=Depends(require_user)):
|
||||
csrf = make_csrf(request.session)
|
||||
return templates.TemplateResponse("compose.html", {"request": request, "csrf": csrf, "user": user})
|
||||
|
||||
@router.post("/compose")
|
||||
async def compose_send(request: Request, to: str = Form(...), subject: str = Form(""), body: str = Form(""), csrf: str = Form(...), action: str = Form("send"), files: list[UploadFile] = File(default=[]), user=Depends(require_user)):
|
||||
check_csrf(request.session, csrf)
|
||||
async with jmap.client() as ac:
|
||||
api = user["api_url"]
|
||||
primary = user.get("primary", {})
|
||||
account_id = primary.get("urn:ietf:params:jmap:mail")
|
||||
# Upload attachments if any
|
||||
upload_url = user.get("upload_url")
|
||||
blobs = []
|
||||
form = await request.form()
|
||||
for k, v in form.multi_items():
|
||||
if k == 'preblob':
|
||||
try:
|
||||
b = json.loads(v)
|
||||
if b.get('blobId'): blobs.append(b)
|
||||
except Exception:
|
||||
pass
|
||||
if files:
|
||||
for f in files:
|
||||
data = await f.read()
|
||||
if upload_url:
|
||||
url = upload_url.replace("{accountId}", account_id or "")
|
||||
ru = await ac.post(url, content=data, headers={"Content-Type": f.content_type or "application/octet-stream"}, auth=tuple(user["auth"]))
|
||||
ru.raise_for_status()
|
||||
up = ru.json()
|
||||
blobs.append({"blobId": up.get("blobId"), "type": f.content_type or "application/octet-stream", "name": f.filename, "size": len(data)})
|
||||
email_creation_id = "k1"
|
||||
submission_creation_id = "k2"
|
||||
create_email = {
|
||||
"accountId": account_id,
|
||||
"create": {
|
||||
email_creation_id: {
|
||||
"mailboxIds": {},
|
||||
"from": [{"email": user["username"]}],
|
||||
"to": [{"email": x.strip()} for x in to.split(",") if x.strip()],
|
||||
"subject": subject,
|
||||
"textBody": body,
|
||||
"attachments": [{"blobId": b["blobId"], "type": b["type"], "name": b["name"]} for b in blobs]
|
||||
}
|
||||
}
|
||||
}
|
||||
# Move to Drafts if requested, else submit and move to Sent
|
||||
special = await get_special_mailboxes(ac, api, tuple(user["auth"]), account_id)
|
||||
sent_id = special.get("sent")
|
||||
drafts_id = special.get("drafts")
|
||||
calls = []
|
||||
calls.append(["Email/set", create_email, "s1"])
|
||||
if action == "draft":
|
||||
if drafts_id:
|
||||
calls.append(["Email/set", {"accountId": account_id, "onSuccessUpdateEmail": {"#kEmail": {"mailboxIds": {drafts_id: True}}}}, "sdraft"])
|
||||
else:
|
||||
calls.append(["EmailSubmission/set", {"accountId": account_id, "create": {submission_creation_id: {"emailId": {"resultOf":"s1","name":"Email/set","path": f"created/{email_creation_id}/id"}}}}, "s2"])
|
||||
if sent_id:
|
||||
calls.append(["Email/set", {"accountId": account_id, "onSuccessUpdateEmail": {"#kEmail": {"mailboxIds": {sent_id: True}}}}, "ssent"])
|
||||
await jmap.call(ac, api, tuple(user["auth"]), calls)
|
||||
return RedirectResponse("/mail", status_code=303)
|
||||
|
||||
async def get_mailboxes(ac, api, auth, account_id):
|
||||
res = await jmap.call(ac, api, auth, [
|
||||
["Mailbox/query", {"accountId": account_id, "sort":[{"property":"sortOrder","isAscending": True},{"property":"name","isAscending": True}], "limit": 200}, "q1"],
|
||||
["Mailbox/get", {"accountId": account_id, "#ids": {"resultOf":"q1","name":"Mailbox/query","path":"ids"}, "properties":["id","name","role","totalEmails","unreadEmails"]}, "g1"]
|
||||
])
|
||||
boxes = []
|
||||
inbox_id = None
|
||||
for name, data, _ in res.get("methodResponses", []):
|
||||
if name == "Mailbox/get":
|
||||
for b in data.get("list", []):
|
||||
boxes.append({"id": b["id"], "name": b.get("name",""), "role": b.get("role"), "total": b.get("totalEmails",0), "unread": b.get("unreadEmails",0)})
|
||||
if b.get("role") == "inbox":
|
||||
inbox_id = b["id"]
|
||||
return boxes, inbox_id or (boxes[0]["id"] if boxes else None)
|
||||
|
||||
@router.get("/mail/{email_id}/attach/{index}")
|
||||
async def download_attachment(request: Request, email_id: str, index: int, user=Depends(require_user)):
|
||||
atts = request.query_params.get("atts")
|
||||
# Re-fetch message to resolve bodyStructure (simple approach; could cache)
|
||||
async with jmap.client() as ac:
|
||||
api = user["api_url"]
|
||||
res = await jmap.call(ac, api, tuple(user["auth"]), [
|
||||
["Email/get", {"ids": [email_id], "properties": ["bodyStructure"]}, "c1"]
|
||||
])
|
||||
bstruct = None
|
||||
cid_map = {}
|
||||
for name, data, _ in res.get("methodResponses", []):
|
||||
if name == "Email/get":
|
||||
lst = data.get("list", [])
|
||||
if lst:
|
||||
bstruct = lst[0].get("bodyStructure")
|
||||
parts = []
|
||||
def walk(bs, out):
|
||||
if not isinstance(bs, dict): return
|
||||
if bs.get("disposition") == "attachment":
|
||||
out.append(bs)
|
||||
for p in bs.get("subParts", []) or []:
|
||||
walk(p, out)
|
||||
walk(bstruct, parts)
|
||||
if index < 0 or index >= len(parts):
|
||||
raise HTTPException(status_code=404, detail="Attachment not found")
|
||||
p = parts[index]
|
||||
blob = p.get("blobId")
|
||||
name = p.get("name") or "attachment"
|
||||
ctype = p.get("type") or "application/octet-stream"
|
||||
|
||||
# Build download URL from session template
|
||||
tmpl = user.get("download_url") or ""
|
||||
primary = user.get("primary", {})
|
||||
account_id = primary.get("urn:ietf:params:jmap:mail")
|
||||
url = tmpl
|
||||
if "{accountId}" in url:
|
||||
url = url.replace("{accountId}", account_id or "")
|
||||
if "{blobId}" in url:
|
||||
url = url.replace("{blobId}", blob or "")
|
||||
if "{name}" in url:
|
||||
from urllib.parse import quote
|
||||
url = url.replace("{name}", quote(name))
|
||||
# Fallback naive pattern if template missing
|
||||
if not url or "{" in url:
|
||||
from urllib.parse import urljoin, quote
|
||||
base = user.get("jmap_base")
|
||||
url = urljoin(base, f"/download/{quote(account_id or '')}/{quote(blob or '')}/{quote(name)}")
|
||||
|
||||
async with jmap.client() as ac:
|
||||
r = await ac.get(url, auth=tuple(user["auth"]))
|
||||
r.raise_for_status()
|
||||
return StreamingResponse(io.BytesIO(r.content), media_type=ctype, headers={"Content-Disposition": f'attachment; filename="{name}"'})
|
||||
|
||||
|
||||
async def get_special_mailboxes(ac, api, auth, account_id):
|
||||
res = await jmap.call(ac, api, auth, [
|
||||
["Mailbox/query", {"accountId": account_id, "limit": 200}, "q1"],
|
||||
["Mailbox/get", {"accountId": account_id, "#ids": {"resultOf":"q1","name":"Mailbox/query","path":"ids"}, "properties":["id","role","name"]}, "g1"]
|
||||
])
|
||||
sent_id = drafts_id = inbox_id = None
|
||||
boxes = {}
|
||||
for name, data, _ in res.get("methodResponses", []):
|
||||
if name == "Mailbox/get":
|
||||
for b in data.get("list", []):
|
||||
boxes[b["id"]] = b
|
||||
role = b.get("role")
|
||||
if role == "sent": sent_id = b["id"]
|
||||
if role == "drafts": drafts_id = b["id"]
|
||||
if role == "inbox": inbox_id = b["id"]
|
||||
return {"sent": sent_id, "drafts": drafts_id, "inbox": inbox_id, "all": boxes}
|
||||
|
||||
|
||||
@router.get("/mail/{email_id}/cid/{cid}")
|
||||
async def fetch_cid(request: Request, email_id: str, cid: str, user=Depends(require_user)):
|
||||
# Walk bodyStructure to find matching cid, then download via downloadUrl
|
||||
async with jmap.client() as ac:
|
||||
api = user["api_url"]
|
||||
res = await jmap.call(ac, api, tuple(user["auth"]), [
|
||||
["Email/get", {"ids": [email_id], "properties": ["bodyStructure"]}, "c1"]
|
||||
])
|
||||
bstruct = None
|
||||
for name, data, _ in res.get("methodResponses", []):
|
||||
if name == "Email/get":
|
||||
lst = data.get("list", [])
|
||||
if lst:
|
||||
bstruct = lst[0].get("bodyStructure")
|
||||
target = None
|
||||
def walk(bs):
|
||||
nonlocal target
|
||||
if not isinstance(bs, dict) or target is not None: return
|
||||
if bs.get("cid") and bs.get("cid").strip("<>") == cid:
|
||||
target = bs
|
||||
return
|
||||
for p in bs.get("subParts", []) or []:
|
||||
walk(p)
|
||||
walk(bstruct)
|
||||
if not target:
|
||||
raise HTTPException(status_code=404, detail="Inline part not found")
|
||||
blob = target.get("blobId")
|
||||
ctype = target.get("type") or "application/octet-stream"
|
||||
name = target.get("name") or "inline"
|
||||
tmpl = user.get("download_url") or ""
|
||||
primary = user.get("primary", {})
|
||||
account_id = primary.get("urn:ietf:params:jmap:mail")
|
||||
from urllib.parse import quote, urljoin
|
||||
if tmpl and "{accountId}" in tmpl and "{blobId}" in tmpl:
|
||||
url = tmpl.replace("{accountId}", account_id or "").replace("{blobId}", blob or "")
|
||||
if "{name}" in url:
|
||||
url = url.replace("{name}", quote(name))
|
||||
else:
|
||||
url = urljoin(user.get("jmap_base"), f"/download/{quote(account_id or '')}/{quote(blob or '')}/{quote(name)}")
|
||||
async with jmap.client() as ac:
|
||||
r = await ac.get(url, auth=tuple(user["auth"]))
|
||||
r.raise_for_status()
|
||||
return StreamingResponse(io.BytesIO(r.content), media_type=ctype)
|
||||
|
||||
|
||||
@router.post("/upload")
|
||||
async def upload_file(request: Request, file: UploadFile = File(...), user=Depends(require_user)):
|
||||
async with jmap.client() as ac:
|
||||
primary = user.get("primary", {})
|
||||
account_id = user.get("active_account") or primary.get("urn:ietf:params:jmap:mail")
|
||||
upload_url = user.get("upload_url")
|
||||
if not upload_url or not account_id:
|
||||
raise HTTPException(status_code=400, detail="Upload not available")
|
||||
url = upload_url.replace("{accountId}", account_id)
|
||||
data = await file.read()
|
||||
r = await ac.post(url, content=data, headers={"Content-Type": file.content_type or "application/octet-stream"}, auth=tuple(user["auth"]))
|
||||
r.raise_for_status()
|
||||
up = r.json()
|
||||
return JSONResponse({"blobId": up.get("blobId"), "type": file.content_type or "application/octet-stream", "name": file.filename, "size": len(data)})
|
||||
@@ -0,0 +1,21 @@
|
||||
from typing import Optional
|
||||
from fastapi import APIRouter, Request, Depends, HTTPException
|
||||
from fastapi.responses import HTMLResponse
|
||||
from fastapi.templating import Jinja2Templates
|
||||
import pathlib
|
||||
from .. import dav, jmap, config
|
||||
|
||||
router = APIRouter()
|
||||
templates = Jinja2Templates(directory=str(pathlib.Path(__file__).resolve().parent.parent / "templates"))
|
||||
|
||||
def require_user(request: Request):
|
||||
user = request.session.get("user")
|
||||
if not user:
|
||||
raise HTTPException(status_code=401)
|
||||
return user
|
||||
|
||||
@router.get("/webdav", response_class=HTMLResponse)
|
||||
async def webdav_browse(request: Request, path: Optional[str]=None, user=Depends(require_user)):
|
||||
async with jmap.client() as ac:
|
||||
items = await dav.propfind(ac, config.WEBDAV_BASE, path, tuple(user["auth"]))
|
||||
return templates.TemplateResponse("webdav.html", {"request": request, "items": items, "base": config.WEBDAV_BASE, "user": user})
|
||||
Reference in New Issue
Block a user