From 50cd873bdc8db61bbd92d237091ae172aa1d8575 Mon Sep 17 00:00:00 2001 From: Tiago Garcia Date: Wed, 13 Nov 2024 02:49:43 +0000 Subject: [PATCH] Restructure for flask_sqlalchemy Signed-off-by: Tiago Garcia --- delivery1/server/app.py | 20 +++ delivery1/server/database.py | 4 + delivery1/server/database/__init__.py | 11 -- delivery1/server/database/setup_db.py | 13 -- delivery1/server/models/file.py | 39 +++-- delivery1/server/models/org.py | 26 ++-- delivery1/server/models/user.py | 34 +++-- delivery1/server/requirements.txt | 2 +- delivery1/server/routes/__init__.py | 3 + delivery1/server/routes/file.py | 4 + delivery1/server/routes/org.py | 16 ++ delivery1/server/routes/user.py | 4 + delivery1/server/services/files.py | 51 ++++--- delivery1/server/services/orgs.py | 35 ++--- delivery1/server/services/users.py | 31 ++-- delivery1/server/tests/test_db.py | 212 -------------------------- 16 files changed, 173 insertions(+), 332 deletions(-) create mode 100644 delivery1/server/app.py create mode 100644 delivery1/server/database.py delete mode 100644 delivery1/server/database/__init__.py delete mode 100644 delivery1/server/database/setup_db.py create mode 100644 delivery1/server/routes/__init__.py create mode 100644 delivery1/server/routes/file.py create mode 100644 delivery1/server/routes/org.py create mode 100644 delivery1/server/routes/user.py delete mode 100644 delivery1/server/tests/test_db.py diff --git a/delivery1/server/app.py b/delivery1/server/app.py new file mode 100644 index 0000000..0bcbb7e --- /dev/null +++ b/delivery1/server/app.py @@ -0,0 +1,20 @@ +from flask import Flask +from routes import org_bp, user_bp, file_bp +from database import db_connection +from models import Organization, User, File + +app = Flask(__name__) +app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///database.db" +app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = True +app.config["SQLALCHEMY_AUTOCOMMIT"] = False +app.config["SQLALCHEMY_AUTOFLUSH"] = False +db_connection.init_app(app) +with app.app_context(): + db_connection.create_all() + +app.register_blueprint(org_bp, url_prefix="/org") +app.register_blueprint(user_bp, url_prefix="/user") +app.register_blueprint(file_bp, url_prefix="/file") + +if __name__ == "__main__": + app.run(debug=True) \ No newline at end of file diff --git a/delivery1/server/database.py b/delivery1/server/database.py new file mode 100644 index 0000000..06dd911 --- /dev/null +++ b/delivery1/server/database.py @@ -0,0 +1,4 @@ +from flask_sqlalchemy import SQLAlchemy + +db_connection = SQLAlchemy() +db = db_connection.session \ No newline at end of file diff --git a/delivery1/server/database/__init__.py b/delivery1/server/database/__init__.py deleted file mode 100644 index e6505fe..0000000 --- a/delivery1/server/database/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -import os -from sqlalchemy import create_engine -from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import sessionmaker - -script_dir = os.path.dirname(os.path.abspath(__file__)) -DATABASE_URL = f"sqlite:///{os.path.join(script_dir, 'database.db')}" - -engine = create_engine(DATABASE_URL) -SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) -Base = declarative_base() \ No newline at end of file diff --git a/delivery1/server/database/setup_db.py b/delivery1/server/database/setup_db.py deleted file mode 100644 index 4f43de5..0000000 --- a/delivery1/server/database/setup_db.py +++ /dev/null @@ -1,13 +0,0 @@ -import os -from database import Base, engine -from models import Organization, User, File - -def setup_db(): - db_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "database.db") - if os.path.exists(db_path): - os.remove(db_path) - Base.metadata.create_all(bind=engine) - - -if __name__ == "__main__": - setup_db() \ No newline at end of file diff --git a/delivery1/server/models/file.py b/delivery1/server/models/file.py index 0b7f63a..b99847e 100644 --- a/delivery1/server/models/file.py +++ b/delivery1/server/models/file.py @@ -1,19 +1,28 @@ -from sqlalchemy import Integer, String, ForeignKey -from sqlalchemy.orm import relationship, Mapped, mapped_column -from database import Base -from dataclasses import dataclass +from database import db_connection -@dataclass -class File(Base): +class File(db_connection.Model): __tablename__ = 'files' - id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) - file_handle: Mapped[str] = mapped_column(String, unique=True, nullable=False) - document_handle: Mapped[str] = mapped_column(String, unique=True, nullable=True) - name: Mapped[str] = mapped_column(String, nullable=False) - created_at: Mapped[int] = mapped_column(Integer, nullable=False) - org_id: Mapped[int] = mapped_column(Integer, ForeignKey('organizations.id'), nullable=False) - creator_id: Mapped[int] = mapped_column(Integer, ForeignKey('users.id'), nullable=False) - org: Mapped['Organization'] = relationship('Organization', back_populates='files') - creator: Mapped['User'] = relationship('User', back_populates='files') + id = db_connection.Column(db_connection.Integer, primary_key=True, index=True) + file_handle = db_connection.Column(db_connection.String, unique=True, nullable=False) + document_handle = db_connection.Column(db_connection.String, unique=True, nullable=True) + name = db_connection.Column(db_connection.String, nullable=False) + created_at = db_connection.Column(db_connection.Integer, nullable=False) + # alg: db_connection.Column(db_connection.String, nullable=False) + # key: db_connection.Column(db_connection.String, nullable=False) + org_id = db_connection.Column(db_connection.Integer, db_connection.ForeignKey('organizations.id'), nullable=False) + creator_id = db_connection.Column(db_connection.Integer, db_connection.ForeignKey('users.id'), nullable=False) + org = db_connection.relationship('Organization', back_populates='files') + creator = db_connection.relationship('User', back_populates='files') + + def to_dict(self): + return { + "id": self.id, + "file_handle": self.file_handle, + "document_handle": self.document_handle, + "name": self.name, + "created_at": self.created_at, + "org": {"id": self.org.id, "name": self.org.name}, + "creator": {"id": self.creator.id, "username": self.creator.username} + } \ No newline at end of file diff --git a/delivery1/server/models/org.py b/delivery1/server/models/org.py index dc3adff..b94609f 100644 --- a/delivery1/server/models/org.py +++ b/delivery1/server/models/org.py @@ -1,14 +1,18 @@ -from sqlalchemy import Integer, String, ForeignKey -from sqlalchemy.orm import relationship, Mapped, mapped_column -from database import Base -from dataclasses import dataclass +from database import db_connection -@dataclass -class Organization(Base): +class Organization(db_connection.Model): __tablename__ = 'organizations' - id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) - name: Mapped[str] = mapped_column(String, unique=True, index=True, nullable=False) - owner_id: Mapped[int] = mapped_column(Integer, ForeignKey('users.id'), nullable=False) - owner: Mapped['User'] = relationship('User', back_populates='orgs') - files: Mapped[list['File']] = relationship('File', back_populates='org') \ No newline at end of file + id = db_connection.Column(db_connection.Integer, primary_key=True, index=True) + name = db_connection.Column(db_connection.String, unique=True, index=True, nullable=False) + owner_id = db_connection.Column(db_connection.Integer, db_connection.ForeignKey('users.id'), nullable=False) + owner = db_connection.relationship('User', back_populates='orgs') + files = db_connection.relationship('File', back_populates='org') + + def to_dict(self): + return { + "id": self.id, + "name": self.name, + "owner": self.owner.to_dict(), + "files": [{"id": file.id, "name": file.name, "file_handle": file.file_handle} for file in self.files] + } \ No newline at end of file diff --git a/delivery1/server/models/user.py b/delivery1/server/models/user.py index e4fe370..13b9a0e 100644 --- a/delivery1/server/models/user.py +++ b/delivery1/server/models/user.py @@ -1,16 +1,24 @@ -from sqlalchemy import Integer, String -from sqlalchemy.orm import relationship, Mapped, mapped_column -from database import Base -from dataclasses import dataclass +from database import db_connection -@dataclass -class User(Base): + +class User(db_connection.Model): __tablename__ = 'users' - id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) - username: Mapped[str] = mapped_column(String, unique=True, index=True, nullable=False) - full_name: Mapped[str] = mapped_column(String, nullable=False) - email: Mapped[str] = mapped_column(String, unique=True, index=True, nullable=False) - public_key: Mapped[str] = mapped_column(String, nullable=False) - orgs: Mapped[list['Organization']] = relationship('Organization', back_populates='owner') - files: Mapped[list['File']] = relationship('File', back_populates='creator') \ No newline at end of file + id = db_connection.Column(db_connection.Integer, primary_key=True, index=True) + username = db_connection.Column(db_connection.String, unique=True, index=True, nullable=False) + full_name = db_connection.Column(db_connection.String, nullable=False) + email = db_connection.Column(db_connection.String, unique=True, index=True, nullable=False) + public_key = db_connection.Column(db_connection.String, nullable=False) + orgs = db_connection.relationship('Organization', back_populates='owner') + files = db_connection.relationship('File', back_populates='creator') + + def to_dict(self): + return { + "id": self.id, + "username": self.username, + "full_name": self.full_name, + "email": self.email, + "public_key": self.public_key, + "orgs": [{"id": org.id, "name": org.name} for org in self.orgs], + "files": [{"id": file.id, "name": file.name, "file_handle": file.file_handle} for file in self.files] + } \ No newline at end of file diff --git a/delivery1/server/requirements.txt b/delivery1/server/requirements.txt index 4d682eb..3139099 100644 --- a/delivery1/server/requirements.txt +++ b/delivery1/server/requirements.txt @@ -1,3 +1,3 @@ flask -sqlalchemy +flask_sqlalchemy pytest \ No newline at end of file diff --git a/delivery1/server/routes/__init__.py b/delivery1/server/routes/__init__.py new file mode 100644 index 0000000..295cf24 --- /dev/null +++ b/delivery1/server/routes/__init__.py @@ -0,0 +1,3 @@ +from .org import org_bp +from .user import user_bp +from .file import file_bp \ No newline at end of file diff --git a/delivery1/server/routes/file.py b/delivery1/server/routes/file.py new file mode 100644 index 0000000..558ba0d --- /dev/null +++ b/delivery1/server/routes/file.py @@ -0,0 +1,4 @@ +from flask import Blueprint, request, jsonify +from services import FileService + +file_bp = Blueprint("file", __name__) diff --git a/delivery1/server/routes/org.py b/delivery1/server/routes/org.py new file mode 100644 index 0000000..db8d3dc --- /dev/null +++ b/delivery1/server/routes/org.py @@ -0,0 +1,16 @@ +from flask import Blueprint, request, jsonify +from services import OrganizationService + +org_bp = Blueprint("org", __name__) + +@org_bp.route("/create", methods=["POST"]) +def create(): + data = request.json + org = OrganizationService.create_organization( + name=data["name"], + username=data["username"], + full_name=data["full_name"], + email=data["email"], + public_key=data["public_key"] + ) + return jsonify(org.to_dict()), 201 diff --git a/delivery1/server/routes/user.py b/delivery1/server/routes/user.py new file mode 100644 index 0000000..2db0730 --- /dev/null +++ b/delivery1/server/routes/user.py @@ -0,0 +1,4 @@ +from flask import Blueprint, request, jsonify +from services import UserService + +user_bp = Blueprint("user", __name__) diff --git a/delivery1/server/services/files.py b/delivery1/server/services/files.py index 4a72d0d..83c465b 100644 --- a/delivery1/server/services/files.py +++ b/delivery1/server/services/files.py @@ -2,16 +2,13 @@ import os from datetime import datetime from typing import List, Type -from sqlalchemy.orm import Session +from database import db from models import File, Organization, User class FileService: - def __init__(self, db: Session): - self.db = db - self.repos = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "repository") - - def create_dummy_file(self, org: Organization, user: User) -> File: + @staticmethod + def create_dummy_file(org: Organization, user: User) -> File: file = File( file_handle = "dummy_file", document_handle = "org/dummy_file.txt", @@ -23,34 +20,40 @@ class FileService: creator = user ) - file_path = os.path.join(self.repos, file.document_handle) + file_path = os.path.join(os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "repository"), file.document_handle) with open(file_path, "w") as f: f.write("Dummy file content") - self.db.add(file) - self.db.commit() - self.db.refresh(file) + db.add(file) + db.commit() + db.refresh(file) return file - def get_file(self, file_id: int) -> File | None: - return self.db.query(File).filter(File.id == file_id).first() + @staticmethod + def get_file(file_id: int) -> File | None: + return db.query(File).filter(File.id == file_id).first() - def get_file_by_document_handle(self, document_handle: str) -> File | None: - return self.db.query(File).filter(File.document_handle == document_handle).first() + @staticmethod + def get_file_by_document_handle(document_handle: str) -> File | None: + return db.query(File).filter(File.document_handle == document_handle).first() - def get_file_by_file_handle(self, file_handle: str) -> File | None: - return self.db.query(File).filter(File.file_handle == file_handle).first() + @staticmethod + def get_file_by_file_handle(file_handle: str) -> File | None: + return db.query(File).filter(File.file_handle == file_handle).first() - def list_files(self) -> list[Type[File]]: - return self.db.query(File).all() + @staticmethod + def list_files() -> list[Type[File]]: + return db.query(File).all() - def list_files_in_org(self, org: Organization) -> list[Type[File]]: - return self.db.query(File).filter(File.org_id == org.id).all() + @staticmethod + def list_files_in_org(org: Organization) -> list[Type[File]]: + return db.query(File).filter(File.org_id == org.id).all() - def delete_file(self, file: File) -> File: - file_path = os.path.join(self.repos, file.document_handle) + @staticmethod + def delete_file(file: File) -> File: + file_path = os.path.join(os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "repository"), file.document_handle) os.remove(file_path) file.document_handle = None - self.db.commit() - self.db.refresh(file) + db.commit() + db.refresh(file) return file \ No newline at end of file diff --git a/delivery1/server/services/orgs.py b/delivery1/server/services/orgs.py index bdd668d..deae293 100644 --- a/delivery1/server/services/orgs.py +++ b/delivery1/server/services/orgs.py @@ -1,18 +1,16 @@ import os.path -from sqlalchemy.orm import Session +from database import db from models import Organization class OrganizationService: - def __init__(self, db: Session): - self.db = db - - def create_organization(self, name: str, username: str, full_name: str, email: str, public_key: str) -> Organization: + @staticmethod + def create_organization(name: str, username: str, full_name: str, email: str, public_key: str) -> Organization: from services import UserService - user = UserService(self.db).get_user_by_username(username) + user = UserService().get_user_by_username(username) if not user: - user = UserService(self.db).create_user(username, full_name, email, public_key) + user = UserService().create_user(username, full_name, email, public_key) project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) repos = os.path.join(project_root, "repository") @@ -25,19 +23,22 @@ class OrganizationService: owner=user ) - self.db.add(organization) - self.db.commit() - self.db.refresh(organization) + db.add(organization) + db.commit() + db.refresh(organization) - UserService(self.db).add_org_to_user(user, organization) + UserService().add_org_to_user(user, organization) return organization - def get_organization(self, org_id: int) -> Organization | None: - return self.db.query(Organization).filter(Organization.id == org_id).first() + @staticmethod + def get_organization(org_id: int) -> Organization | None: + return db.query(Organization).filter(Organization.id == org_id).first() - def get_organization_by_name(self, name: str) -> Organization | None: - return self.db.query(Organization).filter(Organization.name == name).first() + @staticmethod + def get_organization_by_name(name: str) -> Organization | None: + return db.query(Organization).filter(Organization.name == name).first() - def list_organizations(self): - return self.db.query(Organization).all() \ No newline at end of file + @staticmethod + def list_organizations(): + return db.query(Organization).all() \ No newline at end of file diff --git a/delivery1/server/services/users.py b/delivery1/server/services/users.py index 2f838ee..9f4e734 100644 --- a/delivery1/server/services/users.py +++ b/delivery1/server/services/users.py @@ -1,12 +1,10 @@ -from sqlalchemy.orm import Session +from database import db from models import User, Organization class UserService: - def __init__(self, db: Session): - self.db = db - - def create_user(self, username: str, full_name: str, email: str, public_key: str, org: Organization = None) -> User: + @staticmethod + def create_user(username: str, full_name: str, email: str, public_key: str, org: Organization = None) -> User: user = User( username=username, full_name=full_name, @@ -14,19 +12,22 @@ class UserService: public_key=public_key, orgs=[org] if org else [] ) - self.db.add(user) - self.db.commit() - self.db.refresh(user) + db.add(user) + db.commit() + db.refresh(user) return user - def get_user(self, user_id: int) -> User | None: - return self.db.query(User).filter(User.id == user_id).first() + @staticmethod + def get_user(user_id: int) -> User | None: + return db.query(User).filter(User.id == user_id).first() - def get_user_by_username(self, username: str) -> User | None: - return self.db.query(User).filter(User.username == username).first() + @staticmethod + def get_user_by_username(username: str) -> User | None: + return db.query(User).filter(User.username == username).first() - def add_org_to_user(self, user: User, org: Organization) -> User: + @staticmethod + def add_org_to_user(user: User, org: Organization) -> User: user.orgs.append(org) - self.db.commit() - self.db.refresh(user) + db.commit() + db.refresh(user) return user \ No newline at end of file diff --git a/delivery1/server/tests/test_db.py b/delivery1/server/tests/test_db.py deleted file mode 100644 index c90c5e2..0000000 --- a/delivery1/server/tests/test_db.py +++ /dev/null @@ -1,212 +0,0 @@ -import os -from pprint import PrettyPrinter - -import database.setup_db -from database import SessionLocal -from services import OrganizationService, UserService, FileService - -database.setup_db.setup_db() - -db = SessionLocal() -org_service = OrganizationService(db) -user_service = UserService(db) -file_service = FileService(db) - -def test_create_organization(): - org = org_service.create_organization( - name="Test Org", - username="testuser", - full_name="Test User", - email="test@mail.com", - public_key="abc123" - ) - - assert org.name == "Test Org" - assert org.owner.username == "testuser" - assert org.owner.full_name == "Test User" - assert org.owner.email == "test@mail.com" - assert org.owner.public_key == "abc123" - assert org.owner.orgs[0].name == "Test Org" - - db.delete(org.owner) - db.delete(org) - db.commit() - - -def test_create_user(): - user = user_service.create_user( - username="testuser", - full_name="Test User", - email="test@mail.com", - public_key="abc123" - ) - - assert user.username == "testuser" - assert user.full_name == "Test User" - assert user.email == "test@mail.com" - assert user.public_key == "abc123" - - db.delete(user) - db.commit() - - -def test_get_organization(): - org = org_service.create_organization( - name="Test Org", - username="testuser", - full_name="Test User", - email="test@mail.com", - public_key="abc123" - ) - - org2 = org_service.get_organization(org.id) - org3 = org_service.get_organization_by_name("Test Org") - - assert org2.name == "Test Org" - assert org3.name == "Test Org" - assert org2.owner.username == "testuser" - assert org3.owner.username == "testuser" - - db.delete(org.owner) - db.delete(org) - db.commit() - - -def test_list_organizations(): - org = org_service.create_organization( - name="Test Org", - username="testuser", - full_name="Test User", - email="test@mail.com", - public_key="abc123" - ) - org2 = org_service.create_organization( - name="Test Org2", - username="testuser2", - full_name="Test User2", - email="mail2@test.com", - public_key="def456" - ) - - orgs = org_service.list_organizations() - - assert len(orgs) == 2 - assert orgs[0].name == "Test Org" - assert orgs[1].name == "Test Org2" - assert orgs == [org, org2] - - db.delete(org.owner) - db.delete(org) - db.delete(org2.owner) - db.delete(org2) - db.commit() - - -def test_get_user(): - user = user_service.create_user( - username="testuser", - full_name="Test User", - email="test@mail.com", - public_key="abc123" - ) - - user2 = user_service.get_user(user.id) - user3 = user_service.get_user_by_username("testuser") - - assert user2.username == "testuser" - assert user3.username == "testuser" - assert user2.full_name == "Test User" - assert user3.full_name == "Test User" - assert user2.email == "test@mail.com" - assert user3.email == "test@mail.com" - assert user2.public_key == "abc123" - assert user3.public_key == "abc123" - - org = org_service.create_organization( - name="Test Org", - username="testuser", - full_name="Test User", - email="test@mail.com", - public_key="abc123" - ) - user4 = user_service.get_user(user.id) - - assert user4.orgs[0].name == "Test Org" - - db.delete(org) - db.delete(user) - db.commit() - -def test_create_dummy_file(): - - org = org_service.create_organization( - name="org", - username="testuser", - full_name="Test User", - email="test@mail.com", - public_key="abc123" - ) - - file = file_service.create_dummy_file(org, org.owner) - - assert file.file_handle == "dummy_file" - assert file.document_handle == "org/dummy_file.txt" - assert file.name == "dummy_file" - project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - repos = os.path.join(project_root, "repository") - with open(os.path.join(repos, file.document_handle), "r") as f: - assert f.read() == "Dummy file content" - - - os.remove(os.path.join(repos, file.document_handle)) - db.delete(file) - db.delete(org.owner) - db.delete(org) - db.commit() - - -def test_get_file(): - org = org_service.create_organization( - name="org", - username="testuser", - full_name="Test User", - email="test@mail.com", - public_key="abc123" - ) - - file = file_service.create_dummy_file(org, org.owner) - file2 = file_service.get_file_by_file_handle("dummy_file") - - assert file2.file_handle == "dummy_file" - assert file2.document_handle == "org/dummy_file.txt" - assert file2.name == "dummy_file" - - project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - repos = os.path.join(project_root, "repository") - os.remove(os.path.join(repos, file.document_handle)) - db.delete(file) - db.delete(org.owner) - db.delete(org) - db.commit() - - -def test_delete_file(): - org = org_service.create_organization( - name="org", - username="testuser", - full_name="Test User", - email="test@mail.com", - public_key="abc123" - ) - - file = file_service.create_dummy_file(org, org.owner) - file2 = file_service.delete_file(file) - - assert file2.file_handle == "dummy_file" - assert file2.document_handle is None - assert os.path.exists(os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "repository", "org", "dummy_file.txt")) == False - - db.delete(file) - db.delete(org.owner) - db.delete(org) - db.commit() \ No newline at end of file