From 929b81ff415c6823e700e1e02169ed2f3f4b7797 Mon Sep 17 00:00:00 2001 From: arkohut <39525455+arkohut@users.noreply.github.com> Date: Sat, 1 Jun 2024 12:52:42 +0800 Subject: [PATCH] feat: update schemas --- memos/crud.py | 26 ++++++++++ memos/models.py | 24 +++++---- memos/schemas.py | 127 +++++++++++++++++++++++++++++++++++++++++++++++ memos/server.py | 88 +++++++++++++++++++++++++++++++- 4 files changed, 253 insertions(+), 12 deletions(-) create mode 100644 memos/crud.py create mode 100644 memos/schemas.py diff --git a/memos/crud.py b/memos/crud.py new file mode 100644 index 0000000..8274fc1 --- /dev/null +++ b/memos/crud.py @@ -0,0 +1,26 @@ +from sqlalchemy.orm import Session +from .schemas import Library, NewLibraryParam, Folder +from .models import LibraryModel, FolderModel + + +def get_library_by_id(library_id: int, db: Session) -> Library | None: + return db.query(Library).filter(Library.id == library_id).first() + + +def create_library(library: NewLibraryParam, db: Session) -> Library: + db_library = LibraryModel(name=library.name) + db.add(db_library) + db.commit() + db.refresh(db_library) + + for folder_path in library.folders: + db_folder = FolderModel(path=str(folder_path), library_id=db_library.id) + db.add(db_folder) + + db.commit() + return Library( + id=db_library.id, + name=db_library.name, + folders=[Folder(id=db_folder.id, name=db_folder.path) for db_folder in db_library.folders], + plugins=[] + ) diff --git a/memos/models.py b/memos/models.py index ab7b32f..0d9af0a 100644 --- a/memos/models.py +++ b/memos/models.py @@ -5,6 +5,7 @@ from sqlalchemy import ( Text, DateTime, Enum, + ForeignKey, func ) from datetime import datetime @@ -30,7 +31,7 @@ class LibraryModel(Base): class FolderModel(Base): __tablename__ = "folders" path: Mapped[str] = mapped_column(String, nullable=False) - library_id: Mapped[int] = mapped_column(Integer, nullable=False) + library_id: Mapped[int] = mapped_column(Integer, ForeignKey('libraries.id'), nullable=False) library: Mapped["LibraryModel"] = relationship("LibraryModel", back_populates="folders") entities: Mapped[List["EntityModel"]] = relationship("EntityModel", back_populates="folder") @@ -44,10 +45,11 @@ class EntityModel(Base): file_last_modified_at: Mapped[datetime] = mapped_column(DateTime, nullable=False) file_type: Mapped[str] = mapped_column(String, nullable=False) last_scan_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) - folder_id: Mapped[int] = mapped_column(Integer, nullable=False) + library_id: Mapped[int] = mapped_column(Integer, ForeignKey('libraries.id'), nullable=False) + folder_id: Mapped[int] = mapped_column(Integer, ForeignKey('folders.id'), nullable=False) folder: Mapped["FolderModel"] = relationship("FolderModel", back_populates="entities") - metadata_entries: Mapped[List["EntityMetadataModel"]] = relationship("EntityMetadataModel", back_populates="entity") - tags: Mapped[List["TagModel"]] = relationship("EntityTagModel", back_populates="entity") + metadata_entries: Mapped[List["EntityMetadataModel"]] = relationship("EntityMetadataModel") + tags: Mapped[List["TagModel"]] = relationship("EntityTagModel") class TagModel(Base): @@ -60,19 +62,19 @@ class TagModel(Base): class EntityTagModel(Base): __tablename__ = "entity_tags" - entity_id: Mapped[int] = mapped_column(Integer, nullable=False) - tag_id: Mapped[int] = mapped_column(Integer, nullable=False) + entity_id: Mapped[int] = mapped_column(Integer, ForeignKey('entities.id'), nullable=False) + tag_id: Mapped[int] = mapped_column(Integer, ForeignKey('tags.id'), nullable=False) source: Mapped[MetadataSource] = mapped_column(Enum(MetadataSource), nullable=False) class EntityMetadataModel(Base): __tablename__ = "metadata_entries" - entity_id: Mapped[int] = mapped_column(Integer, nullable=False) + entity_id: Mapped[int] = mapped_column(Integer, ForeignKey('entities.id'), nullable=False) key: Mapped[str] = mapped_column(String, nullable=False) value: Mapped[str] = mapped_column(Text, nullable=False) source_type: Mapped[MetadataSource] = mapped_column(Enum(MetadataSource), nullable=False) source: Mapped[str | None] = mapped_column(String, nullable=True) - datetype: Mapped[MetadataType] = mapped_column(Enum(MetadataType), nullable=False) + date_type: Mapped[MetadataType] = mapped_column(Enum(MetadataType), nullable=False) entity = relationship("EntityModel", back_populates="metadata_entries") @@ -86,12 +88,12 @@ class PluginModel(Base): class LibraryPluginModel(Base): __tablename__ = "library_plugins" - library_id: Mapped[int] = mapped_column(Integer, nullable=False) - plugin_id: Mapped[int] = mapped_column(Integer, nullable=False) + library_id: Mapped[int] = mapped_column(Integer, ForeignKey('libraries.id'), nullable=False) + plugin_id: Mapped[int] = mapped_column(Integer, ForeignKey('plugins.id'), nullable=False) library: Mapped["LibraryModel"] = relationship("LibraryModel", back_populates="plugins") plugin: Mapped["PluginModel"] = relationship("PluginModel", back_populates="libraries") # Create the database engine with the path from config -engine = create_engine(f"sqlite:///{get_database_path()}", echo=True) +engine = create_engine(f"sqlite:///:memory:", echo=True) Base.metadata.create_all(engine) diff --git a/memos/schemas.py b/memos/schemas.py new file mode 100644 index 0000000..a7853fc --- /dev/null +++ b/memos/schemas.py @@ -0,0 +1,127 @@ +from pydantic import BaseModel, ConfigDict, DirectoryPath, HttpUrl, field_validator +from typing import List +from datetime import datetime +from enum import Enum + + +class MetadataSource(Enum): + USER_GENERATED = "user_generated" + SYSTEM_GENERATED = "system_generated" + PLUGIN_GENERATED = "plugin_generated" + + +class MetadataType(Enum): + EXTRACONTENT = "extra_content" + ATTRIBUTE = "attribute" + + +class NewLibraryParam(BaseModel): + name: str + folders: List[DirectoryPath] = [] + + +class NewFolderParam(BaseModel): + path: DirectoryPath + + +class NewEntityParam(BaseModel): + filename: str + path: str + size: int + created_at: datetime + file_created_at: datetime + file_last_modified_at: datetime + file_type: str + folder_id: int + + +class UpdateTagParam(BaseModel): + description: str | None + color: str | None + + +class UpdateEntityTagsParam(BaseModel): + tags: List[str] = [] + + +class EntityMetadataParam(BaseModel): + key: str + value: str + source: MetadataSource + data_type: MetadataType + + +class UpdateEntityMetadataParam(BaseModel): + metadata_entries: List[EntityMetadataParam] + + +class NewPluginParam(BaseModel): + name: str + description: str | None + webhook_url: HttpUrl + + +class NewLibraryPluginParam(BaseModel): + plugin_id: int + + +class Folder(BaseModel): + id: int + name: str + + model_config = ConfigDict(from_attributes=True) + + +class Plugin(BaseModel): + id: int + name: str + description: str | None + webhook_url: str + + model_config = ConfigDict(from_attributes=True) + + +class Library(BaseModel): + id: int + name: str + folders: List[Folder] = [] + plugins: List[Plugin] = [] + + model_config = ConfigDict(from_attributes=True) + + +class Entity(BaseModel): + id: int + path: str + filename: str + size: int + file_created_at: datetime + file_last_modified_at: datetime + file_type: str + last_scan_at: datetime | None + folder_id: int + library_id: int + + model_config = ConfigDict(from_attributes=True) + + +class Tag(BaseModel): + id: int + name: str + description: str | None + color: str | None + created_at: datetime + source: str + + model_config = ConfigDict(from_attributes=True) + + +class EntityMetadata(BaseModel): + id: int + entity_id: int + key: str + value: str + source: str + date_type: MetadataType + + model_config = ConfigDict(from_attributes=True) diff --git a/memos/server.py b/memos/server.py index 1706d5e..0178de4 100644 --- a/memos/server.py +++ b/memos/server.py @@ -1,4 +1,90 @@ import uvicorn +from fastapi import FastAPI, HTTPException, Depends, status +from sqlalchemy.orm import Session +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker +from typing import List + +from .config import get_database_path +from .crud import get_library_by_id, create_library +from .schemas import ( + Library, + Folder, + Entity, + Plugin, + NewLibraryParam, + NewFolderParam, + NewEntityParam, + NewPluginParam, + NewLibraryPluginParam, +) + +engine = create_engine(f"sqlite:///{get_database_path()}") +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) + +app = FastAPI() + + +def get_db(): + db = SessionLocal() + try: + yield db + finally: + db.close() + + +@app.post("/libraries", response_model=Library) +def new_library(library_param: NewLibraryParam, db: Session = Depends(get_db)): + library = create_library(library_param, db) + return library + + +@app.post("/libraries/{library_id}/folders", response_model=Folder) +def new_folder( + library_id: int, + folder: NewFolderParam, db: Session = Depends(get_db), +): + library = get_library_by_id(library_id, db) + if library is None: + raise HTTPException(status_code=404, detail="Library not found") + + db_folder = Folder(path=folder.path, library_id=library.id) + db.add(db_folder) + db.commit() + db.refresh(db_folder) + return db_folder + + +@app.post("/libraries/{library_id}/entities", response_model=Entity) +def new_entity( + entity: NewEntityParam, library_id: int, db: Session = Depends(get_db) +): + + db_entity = Entity(**entity.model_dump(), library_id=library_id) + db.add(db_entity) + db.commit() + db.refresh(db_entity) + return db_entity + + +@app.post("/plugins", response_model=Plugin) +def new_plugin(plugin: NewPluginParam, db: Session = Depends(get_db)): + db_plugin = Plugin(**plugin.model_dump()) + db.add(db_plugin) + db.commit() + db.refresh(db_plugin) + return db_plugin + + +@app.post("/libraries/{library_id}/plugins", status_code=status.HTTP_204_NO_CONTENT) +def add_library_plugin( + library_id: int, plugin: NewLibraryPluginParam, db: Session = Depends(get_db) +): + db_library_plugin = LibrayPlugin(library_id=library_id, plugin_id=plugin.plugin_id) + db.add(db_library_plugin) + db.commit() + db.refresh(db_library_plugin) + def run_server(): - uvicorn.run("memos.main:app", host="0.0.0.0", port=8080, reload=True) + uvicorn.run("memos.server:app", host="0.0.0.0", port=8080, reload=True)