2025-11-09 23:29:07 +01:00
|
|
|
import argparse
|
|
|
|
|
import uvicorn
|
2025-11-10 15:46:40 +01:00
|
|
|
import logging
|
|
|
|
|
from contextlib import asynccontextmanager
|
2025-11-13 23:22:05 +01:00
|
|
|
from fastapi import FastAPI, Request, HTTPException
|
2025-11-09 23:29:07 +01:00
|
|
|
from fastapi.staticfiles import StaticFiles
|
2025-11-10 01:58:41 +01:00
|
|
|
from fastapi.responses import HTMLResponse, JSONResponse
|
2025-11-09 23:29:07 +01:00
|
|
|
from tortoise.contrib.fastapi import register_tortoise
|
|
|
|
|
from .settings import settings
|
2025-11-13 23:22:05 +01:00
|
|
|
from .routers import (
|
|
|
|
|
auth,
|
|
|
|
|
users,
|
|
|
|
|
folders,
|
|
|
|
|
files,
|
|
|
|
|
shares,
|
|
|
|
|
search,
|
|
|
|
|
admin,
|
|
|
|
|
starred,
|
|
|
|
|
billing,
|
|
|
|
|
admin_billing,
|
|
|
|
|
)
|
2025-11-09 23:29:07 +01:00
|
|
|
from . import webdav
|
2025-11-10 15:46:40 +01:00
|
|
|
from .schemas import ErrorResponse
|
2025-11-13 23:22:05 +01:00
|
|
|
from .middleware.usage_tracking import UsageTrackingMiddleware
|
2025-11-10 01:58:41 +01:00
|
|
|
|
2025-11-13 23:22:05 +01:00
|
|
|
logging.basicConfig(
|
|
|
|
|
level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
|
|
|
)
|
2025-11-10 01:58:41 +01:00
|
|
|
logger = logging.getLogger(__name__)
|
2025-11-09 23:29:07 +01:00
|
|
|
|
2025-11-13 23:22:05 +01:00
|
|
|
|
2025-11-10 15:46:40 +01:00
|
|
|
@asynccontextmanager
|
|
|
|
|
async def lifespan(app: FastAPI):
|
|
|
|
|
logger.info("Starting up...")
|
|
|
|
|
logger.info("Database connected.")
|
|
|
|
|
from .billing.scheduler import start_scheduler
|
|
|
|
|
from .billing.models import PricingConfig
|
2025-11-11 12:47:26 +01:00
|
|
|
from .mail import email_service
|
2025-11-13 23:22:05 +01:00
|
|
|
|
2025-11-10 15:46:40 +01:00
|
|
|
start_scheduler()
|
|
|
|
|
logger.info("Billing scheduler started")
|
2025-11-11 12:47:26 +01:00
|
|
|
await email_service.start()
|
|
|
|
|
logger.info("Email service started")
|
2025-11-10 15:46:40 +01:00
|
|
|
pricing_count = await PricingConfig.all().count()
|
|
|
|
|
if pricing_count == 0:
|
|
|
|
|
from decimal import Decimal
|
2025-11-13 23:22:05 +01:00
|
|
|
|
|
|
|
|
await PricingConfig.create(
|
|
|
|
|
config_key="storage_per_gb_month",
|
|
|
|
|
config_value=Decimal("0.0045"),
|
|
|
|
|
description="Storage cost per GB per month",
|
|
|
|
|
unit="per_gb_month",
|
|
|
|
|
)
|
|
|
|
|
await PricingConfig.create(
|
|
|
|
|
config_key="bandwidth_egress_per_gb",
|
|
|
|
|
config_value=Decimal("0.009"),
|
|
|
|
|
description="Bandwidth egress cost per GB",
|
|
|
|
|
unit="per_gb",
|
|
|
|
|
)
|
|
|
|
|
await PricingConfig.create(
|
|
|
|
|
config_key="bandwidth_ingress_per_gb",
|
|
|
|
|
config_value=Decimal("0.0"),
|
|
|
|
|
description="Bandwidth ingress cost per GB (free)",
|
|
|
|
|
unit="per_gb",
|
|
|
|
|
)
|
|
|
|
|
await PricingConfig.create(
|
|
|
|
|
config_key="free_tier_storage_gb",
|
|
|
|
|
config_value=Decimal("15"),
|
|
|
|
|
description="Free tier storage in GB",
|
|
|
|
|
unit="gb",
|
|
|
|
|
)
|
|
|
|
|
await PricingConfig.create(
|
|
|
|
|
config_key="free_tier_bandwidth_gb",
|
|
|
|
|
config_value=Decimal("15"),
|
|
|
|
|
description="Free tier bandwidth in GB per month",
|
|
|
|
|
unit="gb",
|
|
|
|
|
)
|
|
|
|
|
await PricingConfig.create(
|
|
|
|
|
config_key="tax_rate_default",
|
|
|
|
|
config_value=Decimal("0.0"),
|
|
|
|
|
description="Default tax rate (0 = no tax)",
|
|
|
|
|
unit="percentage",
|
|
|
|
|
)
|
2025-11-10 15:46:40 +01:00
|
|
|
logger.info("Default pricing configuration initialized")
|
|
|
|
|
|
|
|
|
|
yield
|
|
|
|
|
|
|
|
|
|
from .billing.scheduler import stop_scheduler
|
2025-11-13 23:22:05 +01:00
|
|
|
|
2025-11-10 15:46:40 +01:00
|
|
|
stop_scheduler()
|
|
|
|
|
logger.info("Billing scheduler stopped")
|
2025-11-11 12:47:26 +01:00
|
|
|
await email_service.stop()
|
|
|
|
|
logger.info("Email service stopped")
|
2025-11-10 15:46:40 +01:00
|
|
|
print("Shutting down...")
|
|
|
|
|
|
2025-11-13 23:22:05 +01:00
|
|
|
|
2025-11-09 23:29:07 +01:00
|
|
|
app = FastAPI(
|
2025-11-13 12:05:05 +01:00
|
|
|
title="MyWebdav Cloud Storage",
|
|
|
|
|
description="A commercial cloud storage web application",
|
2025-11-09 23:29:07 +01:00
|
|
|
version="0.1.0",
|
2025-11-13 23:22:05 +01:00
|
|
|
lifespan=lifespan,
|
2025-11-09 23:29:07 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
app.include_router(auth.router)
|
|
|
|
|
app.include_router(users.router)
|
|
|
|
|
app.include_router(folders.router)
|
|
|
|
|
app.include_router(files.router)
|
|
|
|
|
app.include_router(shares.router)
|
|
|
|
|
app.include_router(search.router)
|
2025-11-10 15:46:40 +01:00
|
|
|
app.include_router(admin.router)
|
|
|
|
|
app.include_router(starred.router)
|
|
|
|
|
app.include_router(billing.router)
|
|
|
|
|
app.include_router(admin_billing.router)
|
2025-11-09 23:29:07 +01:00
|
|
|
app.include_router(webdav.router)
|
|
|
|
|
|
2025-11-10 15:46:40 +01:00
|
|
|
app.add_middleware(UsageTrackingMiddleware)
|
|
|
|
|
|
2025-11-09 23:29:07 +01:00
|
|
|
app.mount("/static", StaticFiles(directory="static"), name="static")
|
|
|
|
|
|
|
|
|
|
register_tortoise(
|
|
|
|
|
app,
|
|
|
|
|
db_url=settings.DATABASE_URL,
|
2025-11-13 23:22:05 +01:00
|
|
|
modules={"models": ["mywebdav.models"], "billing": ["mywebdav.billing.models"]},
|
2025-11-09 23:29:07 +01:00
|
|
|
generate_schemas=True,
|
|
|
|
|
add_exception_handlers=True,
|
|
|
|
|
)
|
|
|
|
|
|
2025-11-13 23:22:05 +01:00
|
|
|
|
2025-11-10 01:58:41 +01:00
|
|
|
@app.exception_handler(HTTPException)
|
|
|
|
|
async def http_exception_handler(request: Request, exc: HTTPException):
|
2025-11-13 23:22:05 +01:00
|
|
|
logger.error(
|
|
|
|
|
f"HTTPException: {exc.status_code} - {exc.detail} for URL: {request.url}"
|
|
|
|
|
)
|
2025-11-10 01:58:41 +01:00
|
|
|
return JSONResponse(
|
|
|
|
|
status_code=exc.status_code,
|
2025-11-13 23:05:26 +01:00
|
|
|
content=ErrorResponse(code=exc.status_code, message=exc.detail).model_dump(),
|
2025-11-10 01:58:41 +01:00
|
|
|
)
|
|
|
|
|
|
2025-11-09 23:29:07 +01:00
|
|
|
|
2025-11-13 23:22:05 +01:00
|
|
|
@app.get("/", response_class=HTMLResponse) # Change response_class to HTMLResponse
|
2025-11-09 23:29:07 +01:00
|
|
|
async def read_root():
|
|
|
|
|
with open("static/index.html", "r") as f:
|
|
|
|
|
return f.read()
|
|
|
|
|
|
2025-11-13 23:22:05 +01:00
|
|
|
|
2025-11-09 23:29:07 +01:00
|
|
|
def main():
|
2025-11-13 20:50:03 +01:00
|
|
|
parser = argparse.ArgumentParser(description="Run the MyWebdav application.")
|
2025-11-13 23:22:05 +01:00
|
|
|
parser.add_argument(
|
|
|
|
|
"--host", type=str, default="0.0.0.0", help="Host address to bind to"
|
|
|
|
|
)
|
2025-11-09 23:29:07 +01:00
|
|
|
parser.add_argument("--port", type=int, default=8000, help="Port to listen on")
|
|
|
|
|
args = parser.parse_args()
|
|
|
|
|
|
|
|
|
|
uvicorn.run(app, host=args.host, port=args.port)
|
|
|
|
|
|
2025-11-13 23:22:05 +01:00
|
|
|
|
2025-11-09 23:29:07 +01:00
|
|
|
if __name__ == "__main__":
|
|
|
|
|
main()
|