Initial commit: LinkVault – KI-gestützte Link-Sammlung

FastAPI + HTMX Web-App zum Speichern von Links mit automatischer
KI-Kategorisierung, Zusammenfassung und semantischer Suche (OpenAI).
Mehrbenutzer mit Login, SQLite-Persistenz, Docker/Docker-Compose-Setup.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Erik Thiele
2026-07-13 22:07:05 +02:00
parent ff8c9f5141
commit 47685f0d34
22 changed files with 1035 additions and 0 deletions

49
app/models.py Normal file
View File

@@ -0,0 +1,49 @@
import json
from datetime import datetime, timezone
from sqlalchemy import DateTime, ForeignKey, String, Text
from sqlalchemy.orm import Mapped, mapped_column, relationship
from .database import Base
def utcnow() -> datetime:
return datetime.now(timezone.utc)
class User(Base):
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True)
email: Mapped[str] = mapped_column(String(255), unique=True, index=True)
password_hash: Mapped[str] = mapped_column(String(255))
created_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow)
links: Mapped[list["Link"]] = relationship(
back_populates="user", cascade="all, delete-orphan"
)
class Link(Base):
__tablename__ = "links"
id: Mapped[int] = mapped_column(primary_key=True)
user_id: Mapped[int] = mapped_column(ForeignKey("users.id"), index=True)
url: Mapped[str] = mapped_column(Text)
title: Mapped[str] = mapped_column(Text, default="")
summary: Mapped[str] = mapped_column(Text, default="")
category: Mapped[str] = mapped_column(String(120), default="", index=True)
manufacturer: Mapped[str] = mapped_column(String(120), default="", index=True)
tags: Mapped[str] = mapped_column(Text, default="[]") # JSON-Liste
embedding: Mapped[str] = mapped_column(Text, default="") # JSON-Liste (Vektor)
status: Mapped[str] = mapped_column(String(20), default="ok")
created_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow, index=True)
user: Mapped["User"] = relationship(back_populates="links")
@property
def tag_list(self) -> list[str]:
try:
return json.loads(self.tags)
except (ValueError, TypeError):
return []