slugs / main.py
triflix's picture
Update main.py
6fe8a61 verified
from fastapi import FastAPI, Request, Form, Depends, HTTPException
from fastapi.templating import Jinja2Templates
from fastapi.staticfiles import StaticFiles
from fastapi.responses import HTMLResponse, RedirectResponse
from sqlalchemy import create_engine, Column, Integer, String, Boolean, DateTime, Text
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, Session
from datetime import datetime
import re
import os
import bcrypt
import random
import string
from typing import Optional
# Define base URL (update for deployment)
BASE_URL = "https://triflix-slugs.hf.space"
# Create the FastAPI app
app = FastAPI(title="Notepad App")
# Set up templates and static files directories
templates_dir = os.path.join(os.path.dirname(__file__), "templates")
templates = Jinja2Templates(directory=templates_dir)
# Create static directory if it doesn't exist
static_dir = os.path.join(os.path.dirname(__file__), "static")
os.makedirs(static_dir, exist_ok=True)
app.mount("/static", StaticFiles(directory=static_dir), name="static")
# Database setup
DATABASE_URL = "sqlite:///./notepad.db"
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
# Define the Note model (custom slug stored in "code")
class Note(Base):
__tablename__ = "notes"
id = Column(Integer, primary_key=True, index=True)
code = Column(String, unique=True, index=True) # Custom slug
content = Column(Text)
is_private = Column(Boolean, default=False)
password = Column(String, nullable=True)
created_at = Column(DateTime, default=datetime.utcnow)
# Create the database tables
Base.metadata.create_all(bind=engine)
# Dependency to get DB session
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
# Function to detect image URLs in text and convert them to HTML img tags
def process_content(content):
image_pattern = r'(https?://\S+\.(?:jpg|jpeg|png|gif|webp|svg))'
processed_content = re.sub(
image_pattern,
r'<img src="\1" class="my-2 max-w-full h-auto rounded" alt="Image">',
content
)
processed_content = processed_content.replace('\n', '<br>')
return processed_content
# Function to generate a random code for auto-generated slug (6-character)
def generate_random_code(length=6):
characters = string.ascii_letters + string.digits
return ''.join(random.choice(characters) for _ in range(length))
# GET /{slug}: Check for note; if exists, show it; otherwise, show creation form
@app.get("/{slug}", response_class=HTMLResponse)
async def custom_slug(request: Request, slug: str, db: Session = Depends(get_db)):
note = db.query(Note).filter(Note.code == slug).first()
if note:
if note.is_private:
return templates.TemplateResponse("password.html", {"request": request, "slug": slug})
processed_content = process_content(note.content)
return templates.TemplateResponse("view.html", {"request": request, "note": note, "content": processed_content})
else:
# No note exists for this slug; show creation form with slug pre-set.
return templates.TemplateResponse("index.html", {"request": request, "slug": slug})
# POST /create: Create a note with the provided custom slug
@app.post("/create", response_class=HTMLResponse)
async def create_note(
request: Request,
slug: str = Form(...),
content: str = Form(...),
is_private: bool = Form(False),
password: Optional[str] = Form(None),
db: Session = Depends(get_db)
):
# Check if slug already exists
if db.query(Note).filter(Note.code == slug).first():
raise HTTPException(status_code=400, detail="Slug already exists. Choose a different one.")
hashed_password = None
if is_private and password:
hashed_password = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8')
new_note = Note(
code=slug,
content=content,
is_private=is_private,
password=hashed_password
)
db.add(new_note)
db.commit()
return RedirectResponse(url=f"{BASE_URL}/{slug}", status_code=303)
# POST /{slug}/verify: Verify password for private notes
@app.post("/{slug}/verify", response_class=HTMLResponse)
async def verify_password(
request: Request,
slug: str,
password: str = Form(...),
db: Session = Depends(get_db)
):
note = db.query(Note).filter(Note.code == slug).first()
if not note:
raise HTTPException(status_code=404, detail="Note not found")
if not note.is_private or not note.password:
return RedirectResponse(url=f"{BASE_URL}/{slug}", status_code=303)
is_valid = bcrypt.checkpw(password.encode('utf-8'), note.password.encode('utf-8'))
if not is_valid:
return templates.TemplateResponse("password.html", {"request": request, "slug": slug, "error": "Invalid password", "error_effect": True})
processed_content = process_content(note.content)
return templates.TemplateResponse("view.html", {"request": request, "note": note, "content": processed_content})
# Landing page: If no slug provided, auto-generate one for the creation form
@app.get("/", response_class=HTMLResponse)
async def landing(request: Request):
random_slug = generate_random_code()
return templates.TemplateResponse("index.html", {"request": request, "slug": random_slug})
if __name__ == "__main__":
import uvicorn
uvicorn.run("main:app", host="0.0.0.0", port=7860, reload=True)